1 /*******************************************************************************
2 * Copyright (c) 2000, 2018 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 * Andrey Loskutov <loskutov@gmx.de> - bug 488172
14 * Stefan Xenos (Google) - bug 487254 - StyledText.getTopIndex() can return negative values
15 * Angelo Zerr <angelo.zerr@gmail.com> - Customize different line spacing of StyledText - Bug 522020
16 * Karsten Thoms <thoms@itemis.de> - bug 528746 add getOffsetAtPoint(Point)
17 *******************************************************************************/
18 package org.eclipse.swt.custom;
19
20
21 import java.util.*;
22 import java.util.List;
23
24 import org.eclipse.swt.*;
25 import org.eclipse.swt.accessibility.*;
26 import org.eclipse.swt.dnd.*;
27 import org.eclipse.swt.events.*;
28 import org.eclipse.swt.graphics.*;
29 import org.eclipse.swt.internal.*;
30 import org.eclipse.swt.printing.*;
31 import org.eclipse.swt.widgets.*;
32
33 /**
34 * A StyledText is an editable user interface object that displays lines
35 * of text. The following style attributes can be defined for the text:
36 * <ul>
37 * <li>foreground color
38 * <li>background color
39 * <li>font style (bold, italic, bold-italic, regular)
40 * <li>underline
41 * <li>strikeout
42 * </ul>
43 * <p>
44 * In addition to text style attributes, the background color of a line may
45 * be specified.
46 * </p><p>
47 * There are two ways to use this widget when specifying text style information.
48 * You may use the API that is defined for StyledText or you may define your own
49 * LineStyleListener. If you define your own listener, you will be responsible
50 * for maintaining the text style information for the widget. IMPORTANT: You may
51 * not define your own listener and use the StyledText API. The following
52 * StyledText API is not supported if you have defined a LineStyleListener:</p>
53 * <ul>
54 * <li>getStyleRangeAtOffset(int)
55 * <li>getStyleRanges()
56 * <li>replaceStyleRanges(int,int,StyleRange[])
57 * <li>setStyleRange(StyleRange)
58 * <li>setStyleRanges(StyleRange[])
59 * </ul>
60 * <p>
61 * There are two ways to use this widget when specifying line background colors.
62 * You may use the API that is defined for StyledText or you may define your own
63 * LineBackgroundListener. If you define your own listener, you will be responsible
64 * for maintaining the line background color information for the widget.
65 * IMPORTANT: You may not define your own listener and use the StyledText API.
66 * The following StyledText API is not supported if you have defined a
67 * LineBackgroundListener:</p>
68 * <ul>
69 * <li>getLineBackground(int)
70 * <li>setLineBackground(int,int,Color)
71 * </ul>
72 * <p>
73 * The content implementation for this widget may also be user-defined. To do so,
74 * you must implement the StyledTextContent interface and use the StyledText API
75 * setContent(StyledTextContent) to initialize the widget.
76 * </p>
77 * <dl>
78 * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
79 * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey, OrientationChange
80 * </dl>
81 * <p>
82 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
83 * </p>
84 *
85 * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a>
86 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a>
87 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
88 * @noextend This class is not intended to be subclassed by clients.
89 */
90 public class StyledText extends Canvas {
91 static final char TAB = '\t';
92 static final String PlatformLineDelimiter = System.lineSeparator();
93 static final int BIDI_CARET_WIDTH = 3;
94 static final int DEFAULT_WIDTH = 64;
95 static final int DEFAULT_HEIGHT = 64;
96 static final int V_SCROLL_RATE = 50;
97 static final int H_SCROLL_RATE = 10;
98 static final int PREVIOUS_OFFSET_TRAILING = 0;
99 static final int OFFSET_LEADING = 1;
100
101 static final String STYLEDTEXT_KEY = "org.eclipse.swt.internal.cocoa.styledtext"; //$NON-NLS-1$
102
103 Color selectionBackground; // selection background color
104 Color selectionForeground; // selection foreground color
105 StyledTextContent content; // native content (default or user specified)
106 StyledTextRenderer renderer;
107 Listener listener;
108 TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
109 int verticalScrollOffset = 0; // pixel based
110 int horizontalScrollOffset = 0; // pixel based
111 boolean alwaysShowScroll = true;
112 int ignoreResize = 0;
113 int topIndex = 0; // top visible line
114 int topIndexY;
115 int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new visible lines during Resize callback
116 int clientAreaWidth = 0; // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated
117 int tabLength = 4; // number of characters in a tab
118 int [] tabs;
119 int leftMargin;
120 int topMargin;
121 int rightMargin;
122 int bottomMargin;
123 Color marginColor;
124 int columnX; // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
125 int caretOffset;
126 int caretAlignment;
127 Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection (x <= y)
128 Point clipboardSelection; // x and y are start and end caret offsets of previous selection
129 int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text
130 Point doubleClickSelection; // selection after last mouse double click
131 boolean editable = true;
132 boolean wordWrap = false; // text is wrapped automatically
133 boolean visualWrap = false; // process line breaks inside logical lines (inserted by BidiSegmentEvent)
134 boolean hasStyleWithVariableHeight = false;
135 boolean hasVerticalIndent = false;
136 boolean doubleClickEnabled = true; // see getDoubleClickEnabled
137 boolean overwrite = false; // insert/overwrite edit mode
138 int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default.
139 Map<Integer, Integer> keyActionMap = new HashMap<>();
140 Color background = null; // workaround for bug 4791
141 Color foreground = null; //
142 /** True if a non-default background color is set */
143 boolean customBackground;
144 /** True if a non-default foreground color is set */
145 boolean customForeground;
146 /** False iff the widget is disabled */
147 boolean enabled = true;
148 /** True iff the widget is in the midst of being enabled or disabled */
149 boolean insideSetEnableCall;
150 Clipboard clipboard;
151 int clickCount;
152 int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left)
153 int autoScrollDistance = 0;
154 int lastTextChangeStart; // cache data of the
155 int lastTextChangeNewLineCount; // last text changing
156 int lastTextChangeNewCharCount; // event for use in the
157 int lastTextChangeReplaceLineCount; // text changed handler
158 int lastTextChangeReplaceCharCount;
159 int lastCharCount = 0;
160 int lastLineBottom; // the bottom pixel of the last line been replaced
161 boolean bidiColoring = false; // apply the BIDI algorithm on text segments of the same color
162 Image leftCaretBitmap = null;
163 Image rightCaretBitmap = null;
164 int caretDirection = SWT.NULL;
165 int caretWidth = 0;
166 Caret defaultCaret = null;
167 boolean updateCaretDirection = true;
168 boolean dragDetect = true;
169 IME ime;
170 Cursor cursor;
171 int alignment;
172 boolean justify;
173 int indent, wrapIndent;
174 int lineSpacing;
175 int alignmentMargin;
176 int newOrientation = SWT.NONE;
177 int accCaretOffset;
178 Accessible acc;
179 AccessibleControlAdapter accControlAdapter;
180 AccessibleAttributeAdapter accAttributeAdapter;
181 AccessibleEditableTextListener accEditableTextListener;
182 AccessibleTextExtendedAdapter accTextExtendedAdapter;
183 AccessibleAdapter accAdapter;
184 MouseNavigator mouseNavigator;
185 boolean middleClickPressed;
186
187 //block selection
188 boolean blockSelection;
189 int blockXAnchor = -1, blockYAnchor = -1;
190 int blockXLocation = -1, blockYLocation = -1;
191
192 final static boolean IS_MAC, IS_GTK;
193 static {
194 String platform = SWT.getPlatform();
195 IS_MAC = "cocoa".equals(platform);
196 IS_GTK = "gtk".equals(platform);
197 }
198
199 /**
200 * The Printing class implements printing of a range of text.
201 * An instance of <code>Printing</code> is returned in the
202 * StyledText#print(Printer) API. The run() method may be
203 * invoked from any thread.
204 */
205 static class Printing implements Runnable {
206 final static int LEFT = 0; // left aligned header/footer segment
207 final static int CENTER = 1; // centered header/footer segment
208 final static int RIGHT = 2; // right aligned header/footer segment
209
210 Printer printer;
211 StyledTextRenderer printerRenderer;
212 StyledTextPrintOptions printOptions;
213 Rectangle clientArea;
214 FontData fontData;
215 Font printerFont;
216 Map<Resource, Resource> resources;
217 int tabLength;
218 GC gc; // printer GC
219 int pageWidth; // width of a printer page in pixels
220 int startPage; // first page to print
221 int endPage; // last page to print
222 int scope; // scope of print job
223 int startLine; // first (wrapped) line to print
224 int endLine; // last (wrapped) line to print
225 boolean singleLine; // widget single line mode
226 Point selection = null; // selected text
227 boolean mirrored; // indicates the printing gc should be mirrored
228 int lineSpacing;
229 int printMargin;
230
231 /**
232 * Creates an instance of <code>Printing</code>.
233 * Copies the widget content and rendering data that needs
234 * to be requested from listeners.
235 *
236 * @param parent StyledText widget to print.
237 * @param printer printer device to print on.
238 * @param printOptions print options
239 */
Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions)240 Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) {
241 this.printer = printer;
242 this.printOptions = printOptions;
243 this.mirrored = (styledText.getStyle() & SWT.MIRRORED) != 0;
244 singleLine = styledText.isSingleLine();
245 startPage = 1;
246 endPage = Integer.MAX_VALUE;
247 PrinterData data = printer.getPrinterData();
248 scope = data.scope;
249 if (scope == PrinterData.PAGE_RANGE) {
250 startPage = data.startPage;
251 endPage = data.endPage;
252 if (endPage < startPage) {
253 int temp = endPage;
254 endPage = startPage;
255 startPage = temp;
256 }
257 } else if (scope == PrinterData.SELECTION) {
258 selection = styledText.getSelectionRange();
259 }
260 printerRenderer = new StyledTextRenderer(printer, null);
261 printerRenderer.setContent(copyContent(styledText.getContent()));
262 cacheLineData(styledText);
263 }
264 /**
265 * Caches all line data that needs to be requested from a listener.
266 *
267 * @param printerContent <code>StyledTextContent</code> to request
268 * line data for.
269 */
cacheLineData(StyledText styledText)270 void cacheLineData(StyledText styledText) {
271 StyledTextRenderer renderer = styledText.renderer;
272 renderer.copyInto(printerRenderer);
273 fontData = styledText.getFont().getFontData()[0];
274 tabLength = styledText.tabLength;
275 int lineCount = printerRenderer.lineCount;
276 if (styledText.isListening(ST.LineGetBackground) || (styledText.isListening(ST.LineGetSegments)) || styledText.isListening(ST.LineGetStyle)) {
277 StyledTextContent content = printerRenderer.content;
278 for (int i = 0; i < lineCount; i++) {
279 String line = content.getLine(i);
280 int lineOffset = content.getOffsetAtLine(i);
281 StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
282 if (event != null && event.lineBackground != null) {
283 printerRenderer.setLineBackground(i, 1, event.lineBackground);
284 }
285 event = styledText.getBidiSegments(lineOffset, line);
286 if (event != null) {
287 printerRenderer.setLineSegments(i, 1, event.segments);
288 printerRenderer.setLineSegmentChars(i, 1, event.segmentsChars);
289 }
290 event = styledText.getLineStyleData(lineOffset, line);
291 if (event != null) {
292 printerRenderer.setLineIndent(i, 1, event.indent);
293 printerRenderer.setLineAlignment(i, 1, event.alignment);
294 printerRenderer.setLineJustify(i, 1, event.justify);
295 printerRenderer.setLineBullet(i, 1, event.bullet);
296 StyleRange[] styles = event.styles;
297 if (styles != null && styles.length > 0) {
298 printerRenderer.setStyleRanges(event.ranges, styles);
299 }
300 }
301 }
302 }
303 Point screenDPI = styledText.getDisplay().getDPI();
304 Point printerDPI = printer.getDPI();
305 resources = new HashMap<> ();
306 for (int i = 0; i < lineCount; i++) {
307 Color color = printerRenderer.getLineBackground(i, null);
308 if (color != null) {
309 if (printOptions.printLineBackground) {
310 Color printerColor = (Color)resources.get(color);
311 if (printerColor == null) {
312 printerColor = new Color (printer, color.getRGB());
313 resources.put(color, printerColor);
314 }
315 printerRenderer.setLineBackground(i, 1, printerColor);
316 } else {
317 printerRenderer.setLineBackground(i, 1, null);
318 }
319 }
320 int indent = printerRenderer.getLineIndent(i, 0);
321 if (indent != 0) {
322 printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
323 }
324 }
325 StyleRange[] styles = printerRenderer.styles;
326 for (int i = 0; i < printerRenderer.styleCount; i++) {
327 StyleRange style = styles[i];
328 Font font = style.font;
329 if (style.font != null) {
330 Font printerFont = (Font)resources.get(font);
331 if (printerFont == null) {
332 printerFont = new Font (printer, font.getFontData());
333 resources.put(font, printerFont);
334 }
335 style.font = printerFont;
336 }
337 Color color = style.foreground;
338 if (color != null) {
339 Color printerColor = (Color)resources.get(color);
340 if (printOptions.printTextForeground) {
341 if (printerColor == null) {
342 printerColor = new Color (printer, color.getRGB());
343 resources.put(color, printerColor);
344 }
345 style.foreground = printerColor;
346 } else {
347 style.foreground = null;
348 }
349 }
350 color = style.background;
351 if (color != null) {
352 Color printerColor = (Color)resources.get(color);
353 if (printOptions.printTextBackground) {
354 if (printerColor == null) {
355 printerColor = new Color (printer, color.getRGB());
356 resources.put(color, printerColor);
357 }
358 style.background = printerColor;
359 } else {
360 style.background = null;
361 }
362 }
363 if (!printOptions.printTextFontStyle) {
364 style.fontStyle = SWT.NORMAL;
365 }
366 style.rise = style.rise * printerDPI.y / screenDPI.y;
367 GlyphMetrics metrics = style.metrics;
368 if (metrics != null) {
369 metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
370 metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
371 metrics.width = metrics.width * printerDPI.x / screenDPI.x;
372 }
373 }
374 lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
375 if (printOptions.printLineNumbers) {
376 printMargin = 3 * printerDPI.x / screenDPI.x;
377 }
378 }
379 /**
380 * Copies the text of the specified <code>StyledTextContent</code>.
381 *
382 * @param original the <code>StyledTextContent</code> to copy.
383 */
copyContent(StyledTextContent original)384 StyledTextContent copyContent(StyledTextContent original) {
385 StyledTextContent printerContent = new DefaultContent();
386 int insertOffset = 0;
387 for (int i = 0; i < original.getLineCount(); i++) {
388 int insertEndOffset;
389 if (i < original.getLineCount() - 1) {
390 insertEndOffset = original.getOffsetAtLine(i + 1);
391 } else {
392 insertEndOffset = original.getCharCount();
393 }
394 printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset));
395 insertOffset = insertEndOffset;
396 }
397 return printerContent;
398 }
399 /**
400 * Disposes of the resources and the <code>PrintRenderer</code>.
401 */
dispose()402 void dispose() {
403 if (gc != null) {
404 gc.dispose();
405 gc = null;
406 }
407 if (resources != null) {
408 for (Resource resource : resources.values()) {
409 resource.dispose();
410 }
411 resources = null;
412 }
413 if (printerFont != null) {
414 printerFont.dispose();
415 printerFont = null;
416 }
417 if (printerRenderer != null) {
418 printerRenderer.dispose();
419 printerRenderer = null;
420 }
421 }
init()422 void init() {
423 Rectangle trim = printer.computeTrim(0, 0, 0, 0);
424 Point dpi = printer.getDPI();
425
426 printerFont = new Font(printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL);
427 clientArea = printer.getClientArea();
428 pageWidth = clientArea.width;
429 // one inch margin around text
430 clientArea.x = dpi.x + trim.x;
431 clientArea.y = dpi.y + trim.y;
432 clientArea.width -= (clientArea.x + trim.width);
433 clientArea.height -= (clientArea.y + trim.height);
434
435 int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
436 gc = new GC(printer, style);
437 gc.setFont(printerFont);
438 printerRenderer.setFont(printerFont, tabLength);
439 int lineHeight = printerRenderer.getLineHeight();
440 if (printOptions.header != null) {
441 clientArea.y += lineHeight * 2;
442 clientArea.height -= lineHeight * 2;
443 }
444 if (printOptions.footer != null) {
445 clientArea.height -= lineHeight * 2;
446 }
447
448 // TODO not wrapped
449 StyledTextContent content = printerRenderer.content;
450 startLine = 0;
451 endLine = singleLine ? 0 : content.getLineCount() - 1;
452 if (scope == PrinterData.PAGE_RANGE) {
453 int pageSize = clientArea.height / lineHeight;//WRONG
454 startLine = (startPage - 1) * pageSize;
455 } else if (scope == PrinterData.SELECTION) {
456 startLine = content.getLineAtOffset(selection.x);
457 if (selection.y > 0) {
458 endLine = content.getLineAtOffset(selection.x + selection.y - 1);
459 } else {
460 endLine = startLine - 1;
461 }
462 }
463 }
464 /**
465 * Prints the lines in the specified page range.
466 */
print()467 void print() {
468 Color background = gc.getBackground();
469 Color foreground = gc.getForeground();
470 int paintY = clientArea.y;
471 int paintX = clientArea.x;
472 int width = clientArea.width;
473 int page = startPage;
474 int pageBottom = clientArea.y + clientArea.height;
475 int orientation = gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT);
476 TextLayout printLayout = null;
477 if (printOptions.printLineNumbers || printOptions.header != null || printOptions.footer != null) {
478 printLayout = new TextLayout(printer);
479 printLayout.setFont(printerFont);
480 }
481 if (printOptions.printLineNumbers) {
482 int numberingWidth = 0;
483 int count = endLine - startLine + 1;
484 String[] lineLabels = printOptions.lineLabels;
485 if (lineLabels != null) {
486 for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
487 if (lineLabels[i] != null) {
488 printLayout.setText(lineLabels[i]);
489 int lineWidth = printLayout.getBounds().width;
490 numberingWidth = Math.max(numberingWidth, lineWidth);
491 }
492 }
493 } else {
494 StringBuilder buffer = new StringBuilder("0");
495 while ((count /= 10) > 0) buffer.append("0");
496 printLayout.setText(buffer.toString());
497 numberingWidth = printLayout.getBounds().width;
498 }
499 numberingWidth += printMargin;
500 if (numberingWidth > width) numberingWidth = width;
501 paintX += numberingWidth;
502 width -= numberingWidth;
503 }
504 for (int i = startLine; i <= endLine && page <= endPage; i++) {
505 if (paintY == clientArea.y) {
506 printer.startPage();
507 printDecoration(page, true, printLayout);
508 }
509 TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
510 Color lineBackground = printerRenderer.getLineBackground(i, background);
511 int paragraphBottom = paintY + layout.getBounds().height;
512 if (paragraphBottom <= pageBottom) {
513 //normal case, the whole paragraph fits in the current page
514 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
515 paintY = paragraphBottom;
516 } else {
517 int lineCount = layout.getLineCount();
518 while (paragraphBottom > pageBottom && lineCount > 0) {
519 lineCount--;
520 paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
521 }
522 if (lineCount == 0) {
523 //the whole paragraph goes to the next page
524 printDecoration(page, false, printLayout);
525 printer.endPage();
526 page++;
527 if (page <= endPage) {
528 printer.startPage();
529 printDecoration(page, true, printLayout);
530 paintY = clientArea.y;
531 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
532 paintY += layout.getBounds().height;
533 }
534 } else {
535 //draw paragraph top in the current page and paragraph bottom in the next
536 int height = paragraphBottom - paintY;
537 gc.setClipping(clientArea.x, paintY, clientArea.width, height);
538 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
539 gc.setClipping((Rectangle)null);
540 printDecoration(page, false, printLayout);
541 printer.endPage();
542 page++;
543 if (page <= endPage) {
544 printer.startPage();
545 printDecoration(page, true, printLayout);
546 paintY = clientArea.y - height;
547 int layoutHeight = layout.getBounds().height;
548 gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
549 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
550 gc.setClipping((Rectangle)null);
551 paintY += layoutHeight;
552 }
553 }
554 }
555 printerRenderer.disposeTextLayout(layout);
556 }
557 if (page <= endPage && paintY > clientArea.y) {
558 // close partial page
559 printDecoration(page, false, printLayout);
560 printer.endPage();
561 }
562 if (printLayout != null) printLayout.dispose();
563 }
564 /**
565 * Print header or footer decorations.
566 *
567 * @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
568 * @param header true = print the header, false = print the footer
569 */
printDecoration(int page, boolean header, TextLayout layout)570 void printDecoration(int page, boolean header, TextLayout layout) {
571 String text = header ? printOptions.header : printOptions.footer;
572 if (text == null) return;
573 int lastSegmentIndex = 0;
574 for (int i = 0; i < 3; i++) {
575 int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
576 String segment;
577 if (segmentIndex == -1) {
578 segment = text.substring(lastSegmentIndex);
579 printDecorationSegment(segment, i, page, header, layout);
580 break;
581 } else {
582 segment = text.substring(lastSegmentIndex, segmentIndex);
583 printDecorationSegment(segment, i, page, header, layout);
584 lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length();
585 }
586 }
587 }
588 /**
589 * Print one segment of a header or footer decoration.
590 * Headers and footers have three different segments.
591 * One each for left aligned, centered, and right aligned text.
592 *
593 * @param segment decoration segment to print
594 * @param alignment alignment of the segment. 0=left, 1=center, 2=right
595 * @param page page number to print, if specified in the decoration segment.
596 * @param header true = print the header, false = print the footer
597 */
printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout)598 void printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout) {
599 int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
600 if (pageIndex != -1) {
601 int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
602 StringBuilder buffer = new StringBuilder(segment.substring (0, pageIndex));
603 buffer.append (page);
604 buffer.append (segment.substring(pageIndex + pageTagLength));
605 segment = buffer.toString();
606 }
607 if (segment.length() > 0) {
608 layout.setText(segment);
609 int segmentWidth = layout.getBounds().width;
610 int segmentHeight = printerRenderer.getLineHeight();
611 int drawX = 0, drawY;
612 if (alignment == LEFT) {
613 drawX = clientArea.x;
614 } else if (alignment == CENTER) {
615 drawX = (pageWidth - segmentWidth) / 2;
616 } else if (alignment == RIGHT) {
617 drawX = clientArea.x + clientArea.width - segmentWidth;
618 }
619 if (header) {
620 drawY = clientArea.y - segmentHeight * 2;
621 } else {
622 drawY = clientArea.y + clientArea.height + segmentHeight;
623 }
624 layout.draw(gc, drawX, drawY);
625 }
626 }
printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index)627 void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) {
628 if (background != null) {
629 Rectangle rect = layout.getBounds();
630 gc.setBackground(background);
631 gc.fillRectangle(x, y, rect.width, rect.height);
632
633 // int lineCount = layout.getLineCount();
634 // for (int i = 0; i < lineCount; i++) {
635 // Rectangle rect = layout.getLineBounds(i);
636 // rect.x += paintX;
637 // rect.y += paintY + layout.getSpacing();
638 // rect.width = width;//layout bounds
639 // gc.fillRectangle(rect);
640 // }
641 }
642 if (printOptions.printLineNumbers) {
643 FontMetrics metrics = layout.getLineMetrics(0);
644 printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
645 printLayout.setDescent(metrics.getDescent());
646 String[] lineLabels = printOptions.lineLabels;
647 if (lineLabels != null) {
648 if (0 <= index && index < lineLabels.length && lineLabels[index] != null) {
649 printLayout.setText(lineLabels[index]);
650 } else {
651 printLayout.setText("");
652 }
653 } else {
654 printLayout.setText(String.valueOf(index));
655 }
656 int paintX = x - printMargin - printLayout.getBounds().width;
657 printLayout.draw(gc, paintX, y);
658 printLayout.setAscent(-1);
659 printLayout.setDescent(-1);
660 }
661 gc.setForeground(foreground);
662 layout.draw(gc, x, y);
663 }
664 /**
665 * Starts a print job and prints the pages specified in the constructor.
666 */
667 @Override
run()668 public void run() {
669 String jobName = printOptions.jobName;
670 if (jobName == null) {
671 jobName = "Printing";
672 }
673 if (printer.startJob(jobName)) {
674 init();
675 print();
676 dispose();
677 printer.endJob();
678 }
679 }
680 }
681 /**
682 * The <code>RTFWriter</code> class is used to write widget content as
683 * rich text. The implementation complies with the RTF specification
684 * version 1.5.
685 * <p>
686 * toString() is guaranteed to return a valid RTF string only after
687 * close() has been called.
688 * </p><p>
689 * Whole and partial lines and line breaks can be written. Lines will be
690 * formatted using the styles queried from the LineStyleListener, if
691 * set, or those set directly in the widget. All styles are applied to
692 * the RTF stream like they are rendered by the widget. In addition, the
693 * widget font name and size is used for the whole text.
694 * </p>
695 */
696 class RTFWriter extends TextWriter {
697 static final int DEFAULT_FOREGROUND = 0;
698 static final int DEFAULT_BACKGROUND = 1;
699 List<Color> colorTable;
700 List<Font> fontTable;
701
702 /**
703 * Creates a RTF writer that writes content starting at offset "start"
704 * in the document. <code>start</code> and <code>length</code>can be set to specify partial
705 * lines.
706 *
707 * @param start start offset of content to write, 0 based from
708 * beginning of document
709 * @param length length of content to write
710 */
RTFWriter(int start, int length)711 public RTFWriter(int start, int length) {
712 super(start, length);
713 colorTable = new ArrayList<>();
714 fontTable = new ArrayList<>();
715 colorTable.add(getForeground());
716 colorTable.add(getBackground());
717 fontTable.add(getFont());
718 }
719 /**
720 * Closes the RTF writer. Once closed no more content can be written.
721 * <b>NOTE:</b> <code>toString()</code> does not return a valid RTF string until
722 * <code>close()</code> has been called.
723 */
724 @Override
close()725 public void close() {
726 if (!isClosed()) {
727 writeHeader();
728 write("\n}}\0");
729 super.close();
730 }
731 }
732 /**
733 * Returns the index of the specified color in the RTF color table.
734 *
735 * @param color the color
736 * @param defaultIndex return value if color is null
737 * @return the index of the specified color in the RTF color table
738 * or "defaultIndex" if "color" is null.
739 */
getColorIndex(Color color, int defaultIndex)740 int getColorIndex(Color color, int defaultIndex) {
741 if (color == null) return defaultIndex;
742 int index = colorTable.indexOf(color);
743 if (index == -1) {
744 index = colorTable.size();
745 colorTable.add(color);
746 }
747 return index;
748 }
749 /**
750 * Returns the index of the specified color in the RTF color table.
751 *
752 * @param color the color
753 * @param defaultIndex return value if color is null
754 * @return the index of the specified color in the RTF color table
755 * or "defaultIndex" if "color" is null.
756 */
getFontIndex(Font font)757 int getFontIndex(Font font) {
758 int index = fontTable.indexOf(font);
759 if (index == -1) {
760 index = fontTable.size();
761 fontTable.add(font);
762 }
763 return index;
764 }
765 /**
766 * Appends the specified segment of "string" to the RTF data.
767 * Copy from <code>start</code> up to, but excluding, <code>end</code>.
768 *
769 * @param string string to copy a segment from. Must not contain
770 * line breaks. Line breaks should be written using writeLineDelimiter()
771 * @param start start offset of segment. 0 based.
772 * @param end end offset of segment
773 */
write(String string, int start, int end)774 void write(String string, int start, int end) {
775 for (int index = start; index < end; index++) {
776 char ch = string.charAt(index);
777 if (ch > 0x7F) {
778 // write the sub string from the last escaped character
779 // to the current one. Fixes bug 21698.
780 if (index > start) {
781 write(string.substring(start, index));
782 }
783 write("\\u");
784 write(Integer.toString((short) ch));
785 write('?'); // ANSI representation (1 byte long, \\uc1)
786 start = index + 1;
787 } else if (ch == '}' || ch == '{' || ch == '\\') {
788 // write the sub string from the last escaped character
789 // to the current one. Fixes bug 21698.
790 if (index > start) {
791 write(string.substring(start, index));
792 }
793 write('\\');
794 write(ch);
795 start = index + 1;
796 }
797 }
798 // write from the last escaped character to the end.
799 // Fixes bug 21698.
800 if (start < end) {
801 write(string.substring(start, end));
802 }
803 }
804 /**
805 * Writes the RTF header including font table and color table.
806 */
writeHeader()807 void writeHeader() {
808 StringBuilder header = new StringBuilder();
809 FontData fontData = getFont().getFontData()[0];
810 header.append("{\\rtf1\\ansi");
811 // specify code page, necessary for copy to work in bidi
812 // systems that don't support Unicode RTF.
813 String cpg = System.getProperty("file.encoding").toLowerCase();
814 if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
815 cpg = cpg.substring(2, cpg.length());
816 header.append("\\ansicpg");
817 header.append(cpg);
818 }
819 header.append("\\uc1\\deff0{\\fonttbl{\\f0\\fnil ");
820 header.append(fontData.getName());
821 header.append(";");
822 for (int i = 1; i < fontTable.size(); i++) {
823 header.append("\\f");
824 header.append(i);
825 header.append(" ");
826 FontData fd = fontTable.get(i).getFontData()[0];
827 header.append(fd.getName());
828 header.append(";");
829 }
830 header.append("}}\n{\\colortbl");
831 for (Color color : colorTable) {
832 header.append("\\red");
833 header.append(color.getRed());
834 header.append("\\green");
835 header.append(color.getGreen());
836 header.append("\\blue");
837 header.append(color.getBlue());
838 header.append(";");
839 }
840 // some RTF readers ignore the deff0 font tag. Explicitly
841 // set the font for the whole document to work around this.
842 header.append("}\n{\\f0\\fs");
843 // font size is specified in half points
844 header.append(fontData.getHeight() * 2);
845 header.append(" ");
846 write(header.toString(), 0);
847 }
848 /**
849 * Appends the specified line text to the RTF data. Lines will be formatted
850 * using the styles queried from the LineStyleListener, if set, or those set
851 * directly in the widget.
852 *
853 * @param line line text to write as RTF. Must not contain line breaks
854 * Line breaks should be written using writeLineDelimiter()
855 * @param lineOffset offset of the line. 0 based from the start of the
856 * widget document. Any text occurring before the start offset or after the
857 * end offset specified during object creation is ignored.
858 * @exception SWTException <ul>
859 * <li>ERROR_IO when the writer is closed.</li>
860 * </ul>
861 */
862 @Override
writeLine(String line, int lineOffset)863 public void writeLine(String line, int lineOffset) {
864 if (isClosed()) {
865 SWT.error(SWT.ERROR_IO);
866 }
867 int lineIndex = content.getLineAtOffset(lineOffset);
868 int lineAlignment, lineIndent;
869 boolean lineJustify;
870 int[] ranges;
871 StyleRange[] styles;
872 StyledTextEvent event = getLineStyleData(lineOffset, line);
873 if (event != null) {
874 lineAlignment = event.alignment;
875 lineIndent = event.indent;
876 lineJustify = event.justify;
877 ranges = event.ranges;
878 styles = event.styles;
879 } else {
880 lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
881 lineIndent = renderer.getLineIndent(lineIndex, indent);
882 lineJustify = renderer.getLineJustify(lineIndex, justify);
883 ranges = renderer.getRanges(lineOffset, line.length());
884 styles = renderer.getStyleRanges(lineOffset, line.length(), false);
885 }
886 if (styles == null) styles = new StyleRange[0];
887 Color lineBackground = renderer.getLineBackground(lineIndex, null);
888 event = getLineBackgroundData(lineOffset, line);
889 if (event != null && event.lineBackground != null) lineBackground = event.lineBackground;
890 writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify);
891 }
892 /**
893 * Appends the specified line delimiter to the RTF data.
894 *
895 * @param lineDelimiter line delimiter to write as RTF.
896 * @exception SWTException <ul>
897 * <li>ERROR_IO when the writer is closed.</li>
898 * </ul>
899 */
900 @Override
writeLineDelimiter(String lineDelimiter)901 public void writeLineDelimiter(String lineDelimiter) {
902 if (isClosed()) {
903 SWT.error(SWT.ERROR_IO);
904 }
905 write(lineDelimiter, 0, lineDelimiter.length());
906 write("\\par ");
907 }
908 /**
909 * Appends the specified line text to the RTF data.
910 * <p>
911 * Use the colors and font styles specified in "styles" and "lineBackground".
912 * Formatting is written to reflect the text rendering by the text widget.
913 * Style background colors take precedence over the line background color.
914 * Background colors are written using the \chshdng0\chcbpat tag (vs. the \cb tag).
915 * </p>
916 *
917 * @param line line text to write as RTF. Must not contain line breaks
918 * Line breaks should be written using writeLineDelimiter()
919 * @param lineOffset offset of the line. 0 based from the start of the
920 * widget document. Any text occurring before the start offset or after the
921 * end offset specified during object creation is ignored.
922 * @param styles styles to use for formatting. Must not be null.
923 * @param lineBackground line background color to use for formatting.
924 * May be null.
925 */
writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify)926 void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify) {
927 int lineLength = line.length();
928 int startOffset = getStart();
929 int writeOffset = startOffset - lineOffset;
930 if (writeOffset >= lineLength) return;
931 int lineIndex = Math.max(0, writeOffset);
932
933 write("\\fi");
934 write(indent);
935 switch (alignment) {
936 case SWT.LEFT: write("\\ql"); break;
937 case SWT.CENTER: write("\\qc"); break;
938 case SWT.RIGHT: write("\\qr"); break;
939 }
940 if (justify) write("\\qj");
941 write(" ");
942
943 if (lineBackground != null) {
944 write("{\\chshdng0\\chcbpat");
945 write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
946 write(" ");
947 }
948 int endOffset = startOffset + super.getCharCount();
949 int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
950 for (int i = 0; i < styles.length; i++) {
951 StyleRange style = styles[i];
952 int start, end;
953 if (ranges != null) {
954 start = ranges[i << 1] - lineOffset;
955 end = start + ranges[(i << 1) + 1];
956 } else {
957 start = style.start - lineOffset;
958 end = start + style.length;
959 }
960 // skip over partial first line
961 if (end < writeOffset) {
962 continue;
963 }
964 // style starts beyond line end or RTF write end
965 if (start >= lineEndOffset) {
966 break;
967 }
968 // write any unstyled text
969 if (lineIndex < start) {
970 // copy to start of style
971 // style starting beyond end of write range or end of line
972 // is guarded against above.
973 write(line, lineIndex, start);
974 lineIndex = start;
975 }
976 // write styled text
977 write("{\\cf");
978 write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
979 int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
980 if (colorIndex != DEFAULT_BACKGROUND) {
981 write("\\chshdng0\\chcbpat");
982 write(colorIndex);
983 }
984 int fontStyle = style.fontStyle;
985 Font font = style.font;
986 if (font != null) {
987 int fontIndex = getFontIndex(font);
988 write("\\f");
989 write(fontIndex);
990 FontData fontData = font.getFontData()[0];
991 write("\\fs");
992 write(fontData.getHeight() * 2);
993 fontStyle = fontData.getStyle();
994 }
995 if ((fontStyle & SWT.BOLD) != 0) {
996 write("\\b");
997 }
998 if ((fontStyle & SWT.ITALIC) != 0) {
999 write("\\i");
1000 }
1001 if (style.underline) {
1002 write("\\ul");
1003 }
1004 if (style.strikeout) {
1005 write("\\strike");
1006 }
1007 write(" ");
1008 // copy to end of style or end of write range or end of line
1009 int copyEnd = Math.min(end, lineEndOffset);
1010 // guard against invalid styles and let style processing continue
1011 copyEnd = Math.max(copyEnd, lineIndex);
1012 write(line, lineIndex, copyEnd);
1013 if ((fontStyle & SWT.BOLD) != 0) {
1014 write("\\b0");
1015 }
1016 if ((style.fontStyle & SWT.ITALIC) != 0) {
1017 write("\\i0");
1018 }
1019 if (style.underline) {
1020 write("\\ul0");
1021 }
1022 if (style.strikeout) {
1023 write("\\strike0");
1024 }
1025 write("}");
1026 lineIndex = copyEnd;
1027 }
1028 // write unstyled text at the end of the line
1029 if (lineIndex < lineEndOffset) {
1030 write(line, lineIndex, lineEndOffset);
1031 }
1032 if (lineBackground != null) write("}");
1033 }
1034 }
1035 /**
1036 * The <code>TextWriter</code> class is used to write widget content to
1037 * a string. Whole and partial lines and line breaks can be written. To write
1038 * partial lines, specify the start and length of the desired segment
1039 * during object creation.
1040 * <p>
1041 * <b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close()
1042 * has been called.
1043 * </p>
1044 */
1045 class TextWriter {
1046 private StringBuilder buffer;
1047 private int startOffset; // offset of first character that will be written
1048 private int endOffset; // offset of last character that will be written.
1049 // 0 based from the beginning of the widget text.
1050 private boolean isClosed = false;
1051
1052 /**
1053 * Creates a writer that writes content starting at offset "start"
1054 * in the document. <code>start</code> and <code>length</code> can be set to specify partial lines.
1055 *
1056 * @param start start offset of content to write, 0 based from beginning of document
1057 * @param length length of content to write
1058 */
TextWriter(int start, int length)1059 public TextWriter(int start, int length) {
1060 buffer = new StringBuilder(length);
1061 startOffset = start;
1062 endOffset = start + length;
1063 }
1064 /**
1065 * Closes the writer. Once closed no more content can be written.
1066 * <b>NOTE:</b> <code>toString()</code> is not guaranteed to return a valid string unless
1067 * the writer is closed.
1068 */
close()1069 public void close() {
1070 if (!isClosed) {
1071 isClosed = true;
1072 }
1073 }
1074 /**
1075 * Returns the number of characters to write.
1076 * @return the integer number of characters to write
1077 */
getCharCount()1078 public int getCharCount() {
1079 return endOffset - startOffset;
1080 }
1081 /**
1082 * Returns the offset where writing starts. 0 based from the start of
1083 * the widget text. Used to write partial lines.
1084 * @return the integer offset where writing starts
1085 */
getStart()1086 public int getStart() {
1087 return startOffset;
1088 }
1089 /**
1090 * Returns whether the writer is closed.
1091 * @return a boolean specifying whether or not the writer is closed
1092 */
isClosed()1093 public boolean isClosed() {
1094 return isClosed;
1095 }
1096 /**
1097 * Returns the string. <code>close()</code> must be called before <code>toString()</code>
1098 * is guaranteed to return a valid string.
1099 *
1100 * @return the string
1101 */
1102 @Override
toString()1103 public String toString() {
1104 return buffer.toString();
1105 }
1106 /**
1107 * Appends the given string to the data.
1108 */
write(String string)1109 void write(String string) {
1110 buffer.append(string);
1111 }
1112 /**
1113 * Inserts the given string to the data at the specified offset.
1114 * <p>
1115 * Do nothing if "offset" is < 0 or > getCharCount()
1116 * </p>
1117 *
1118 * @param string text to insert
1119 * @param offset offset in the existing data to insert "string" at.
1120 */
write(String string, int offset)1121 void write(String string, int offset) {
1122 if (offset < 0 || offset > buffer.length()) {
1123 return;
1124 }
1125 buffer.insert(offset, string);
1126 }
1127 /**
1128 * Appends the given int to the data.
1129 */
write(int i)1130 void write(int i) {
1131 buffer.append(i);
1132 }
1133 /**
1134 * Appends the given character to the data.
1135 */
write(char i)1136 void write(char i) {
1137 buffer.append(i);
1138 }
1139 /**
1140 * Appends the specified line text to the data.
1141 *
1142 * @param line line text to write. Must not contain line breaks
1143 * Line breaks should be written using writeLineDelimiter()
1144 * @param lineOffset offset of the line. 0 based from the start of the
1145 * widget document. Any text occurring before the start offset or after the
1146 * end offset specified during object creation is ignored.
1147 * @exception SWTException <ul>
1148 * <li>ERROR_IO when the writer is closed.</li>
1149 * </ul>
1150 */
writeLine(String line, int lineOffset)1151 public void writeLine(String line, int lineOffset) {
1152 if (isClosed) {
1153 SWT.error(SWT.ERROR_IO);
1154 }
1155 int writeOffset = startOffset - lineOffset;
1156 int lineLength = line.length();
1157 int lineIndex;
1158 if (writeOffset >= lineLength) {
1159 return; // whole line is outside write range
1160 } else if (writeOffset > 0) {
1161 lineIndex = writeOffset; // line starts before write start
1162 } else {
1163 lineIndex = 0;
1164 }
1165 int copyEnd = Math.min(lineLength, endOffset - lineOffset);
1166 if (lineIndex < copyEnd) {
1167 write(line.substring(lineIndex, copyEnd));
1168 }
1169 }
1170 /**
1171 * Appends the specified line delimiter to the data.
1172 *
1173 * @param lineDelimiter line delimiter to write
1174 * @exception SWTException <ul>
1175 * <li>ERROR_IO when the writer is closed.</li>
1176 * </ul>
1177 */
writeLineDelimiter(String lineDelimiter)1178 public void writeLineDelimiter(String lineDelimiter) {
1179 if (isClosed) {
1180 SWT.error(SWT.ERROR_IO);
1181 }
1182 write(lineDelimiter);
1183 }
1184 }
1185
1186 /**
1187 * Constructs a new instance of this class given its parent
1188 * and a style value describing its behavior and appearance.
1189 * <p>
1190 * The style value is either one of the style constants defined in
1191 * class <code>SWT</code> which is applicable to instances of this
1192 * class, or must be built by <em>bitwise OR</em>'ing together
1193 * (that is, using the <code>int</code> "|" operator) two or more
1194 * of those <code>SWT</code> style constants. The class description
1195 * lists the style constants that are applicable to the class.
1196 * Style bits are also inherited from superclasses.
1197 * </p>
1198 *
1199 * @param parent a widget which will be the parent of the new instance (cannot be null)
1200 * @param style the style of widget to construct
1201 *
1202 * @exception IllegalArgumentException <ul>
1203 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
1204 * </ul>
1205 * @exception SWTException <ul>
1206 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
1207 * </ul>
1208 *
1209 * @see SWT#FULL_SELECTION
1210 * @see SWT#MULTI
1211 * @see SWT#READ_ONLY
1212 * @see SWT#SINGLE
1213 * @see SWT#WRAP
1214 * @see #getStyle
1215 */
StyledText(Composite parent, int style)1216 public StyledText(Composite parent, int style) {
1217 super(parent, checkStyle(style));
1218 // set the fg in the OS to ensure that these are the same as StyledText, necessary
1219 // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
1220 super.setForeground(getForeground());
1221 super.setDragDetect(false);
1222 Display display = getDisplay();
1223 if ((style & SWT.READ_ONLY) != 0) {
1224 setEditable(false);
1225 }
1226 leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
1227 if ((style & SWT.SINGLE) != 0 && (style & SWT.BORDER) != 0) {
1228 leftMargin = topMargin = rightMargin = bottomMargin = 2;
1229 }
1230 alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
1231 if (alignment == 0) alignment = SWT.LEFT;
1232 clipboard = new Clipboard(display);
1233 installDefaultContent();
1234 renderer = new StyledTextRenderer(getDisplay(), this);
1235 renderer.setContent(content);
1236 renderer.setFont(getFont(), tabLength);
1237 ime = new IME(this, SWT.NONE);
1238 defaultCaret = new Caret(this, SWT.NONE);
1239 if ((style & SWT.WRAP) != 0) {
1240 setWordWrap(true);
1241 }
1242 if (isBidiCaret()) {
1243 createCaretBitmaps();
1244 Runnable runnable = () -> {
1245 int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
1246 if (direction == caretDirection) return;
1247 if (getCaret() != defaultCaret) return;
1248 Point newCaretPos = getPointAtOffset(caretOffset);
1249 setCaretLocation(newCaretPos, direction);
1250 };
1251 BidiUtil.addLanguageListener(this, runnable);
1252 }
1253 setCaret(defaultCaret);
1254 calculateScrollBars();
1255 createKeyBindings();
1256 super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
1257 installListeners();
1258 initializeAccessible();
1259 setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
1260 if (IS_MAC) setData(STYLEDTEXT_KEY);
1261 }
1262 /**
1263 * Adds an extended modify listener. An ExtendedModify event is sent by the
1264 * widget when the widget text has changed.
1265 *
1266 * @param extendedModifyListener the listener
1267 * @exception SWTException <ul>
1268 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1269 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1270 * </ul>
1271 * @exception IllegalArgumentException <ul>
1272 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1273 * </ul>
1274 */
addExtendedModifyListener(ExtendedModifyListener extendedModifyListener)1275 public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
1276 checkWidget();
1277 if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1278 StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
1279 addListener(ST.ExtendedModify, typedListener);
1280 }
1281 /**
1282 * Adds a bidirectional segment listener.
1283 * <p>
1284 * A BidiSegmentEvent is sent
1285 * whenever a line of text is measured or rendered. You can
1286 * specify text ranges in the line that should be treated as if they
1287 * had a different direction than the surrounding text.
1288 * This may be used when adjacent segments of right-to-left text should
1289 * not be reordered relative to each other.
1290 * E.g., multiple Java string literals in a right-to-left language
1291 * should generally remain in logical order to each other, that is, the
1292 * way they are stored.
1293 * </p>
1294 *
1295 * @param listener the listener
1296 * @exception SWTException <ul>
1297 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1298 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1299 * </ul>
1300 * @exception IllegalArgumentException <ul>
1301 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1302 * </ul>
1303 * @see BidiSegmentEvent
1304 * @since 2.0
1305 */
addBidiSegmentListener(BidiSegmentListener listener)1306 public void addBidiSegmentListener(BidiSegmentListener listener) {
1307 checkWidget();
1308 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1309 addListener(ST.LineGetSegments, new StyledTextListener(listener));
1310 resetCache(0, content.getLineCount());
1311 setCaretLocation();
1312 super.redraw();
1313 }
1314 /**
1315 * Adds a caret listener. CaretEvent is sent when the caret offset changes.
1316 *
1317 * @param listener the listener
1318 * @exception SWTException <ul>
1319 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1320 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1321 * </ul>
1322 * @exception IllegalArgumentException <ul>
1323 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1324 * </ul>
1325 *
1326 * @since 3.5
1327 */
addCaretListener(CaretListener listener)1328 public void addCaretListener(CaretListener listener) {
1329 checkWidget();
1330 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1331 addListener(ST.CaretMoved, new StyledTextListener(listener));
1332 }
1333 /**
1334 * Adds a line background listener. A LineGetBackground event is sent by the
1335 * widget to determine the background color for a line.
1336 *
1337 * @param listener the listener
1338 * @exception SWTException <ul>
1339 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1340 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1341 * </ul>
1342 * @exception IllegalArgumentException <ul>
1343 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1344 * </ul>
1345 */
addLineBackgroundListener(LineBackgroundListener listener)1346 public void addLineBackgroundListener(LineBackgroundListener listener) {
1347 checkWidget();
1348 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1349 if (!isListening(ST.LineGetBackground)) {
1350 renderer.clearLineBackground(0, content.getLineCount());
1351 }
1352 addListener(ST.LineGetBackground, new StyledTextListener(listener));
1353 }
1354 /**
1355 * Adds a line style listener. A LineGetStyle event is sent by the widget to
1356 * determine the styles for a line.
1357 *
1358 * @param listener the listener
1359 * @exception SWTException <ul>
1360 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1361 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1362 * </ul>
1363 * @exception IllegalArgumentException <ul>
1364 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1365 * </ul>
1366 */
addLineStyleListener(LineStyleListener listener)1367 public void addLineStyleListener(LineStyleListener listener) {
1368 checkWidget();
1369 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1370 if (!isListening(ST.LineGetStyle)) {
1371 setStyleRanges(0, 0, null, null, true);
1372 renderer.clearLineStyle(0, content.getLineCount());
1373 }
1374 addListener(ST.LineGetStyle, new StyledTextListener(listener));
1375 setCaretLocation();
1376 }
1377 /**
1378 * Adds a modify listener. A Modify event is sent by the widget when the widget text
1379 * has changed.
1380 *
1381 * @param modifyListener the listener
1382 * @exception SWTException <ul>
1383 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1384 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1385 * </ul>
1386 * @exception IllegalArgumentException <ul>
1387 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1388 * </ul>
1389 */
addModifyListener(ModifyListener modifyListener)1390 public void addModifyListener(ModifyListener modifyListener) {
1391 checkWidget();
1392 if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1393 addListener(SWT.Modify, new TypedListener(modifyListener));
1394 }
1395 /**
1396 * Adds a paint object listener. A paint object event is sent by the widget when an object
1397 * needs to be drawn.
1398 *
1399 * @param listener the listener
1400 * @exception SWTException <ul>
1401 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1402 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1403 * </ul>
1404 * @exception IllegalArgumentException <ul>
1405 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1406 * </ul>
1407 *
1408 * @since 3.2
1409 *
1410 * @see PaintObjectListener
1411 * @see PaintObjectEvent
1412 */
addPaintObjectListener(PaintObjectListener listener)1413 public void addPaintObjectListener(PaintObjectListener listener) {
1414 checkWidget();
1415 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1416 addListener(ST.PaintObject, new StyledTextListener(listener));
1417 }
1418 /**
1419 * Adds a selection listener. A Selection event is sent by the widget when the
1420 * user changes the selection.
1421 * <p>
1422 * When <code>widgetSelected</code> is called, the event x and y fields contain
1423 * the start and end caret indices of the selection. The selection values returned are visual
1424 * (i.e., x will always always be <= y).
1425 * No event is sent when the caret is moved while the selection length is 0.
1426 * </p><p>
1427 * <code>widgetDefaultSelected</code> is not called for StyledTexts.
1428 * </p>
1429 *
1430 * @param listener the listener which should be notified when the user changes the receiver's selection
1431
1432 * @exception IllegalArgumentException <ul>
1433 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
1434 * </ul>
1435 * @exception SWTException <ul>
1436 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1437 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1438 * </ul>
1439 *
1440 * @see SelectionListener
1441 * @see #removeSelectionListener
1442 * @see SelectionEvent
1443 */
addSelectionListener(SelectionListener listener)1444 public void addSelectionListener(SelectionListener listener) {
1445 checkWidget();
1446 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1447 addListener(SWT.Selection, new TypedListener(listener));
1448 }
1449 /**
1450 * Adds a verify key listener. A VerifyKey event is sent by the widget when a key
1451 * is pressed. The widget ignores the key press if the listener sets the doit field
1452 * of the event to false.
1453 *
1454 * @param listener the listener
1455 * @exception SWTException <ul>
1456 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1457 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1458 * </ul>
1459 * @exception IllegalArgumentException <ul>
1460 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1461 * </ul>
1462 */
addVerifyKeyListener(VerifyKeyListener listener)1463 public void addVerifyKeyListener(VerifyKeyListener listener) {
1464 checkWidget();
1465 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1466 addListener(ST.VerifyKey, new StyledTextListener(listener));
1467 }
1468 /**
1469 * Adds a verify listener. A Verify event is sent by the widget when the widget text
1470 * is about to change. The listener can set the event text and the doit field to
1471 * change the text that is set in the widget or to force the widget to ignore the
1472 * text change.
1473 *
1474 * @param verifyListener the listener
1475 * @exception SWTException <ul>
1476 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1477 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1478 * </ul>
1479 * @exception IllegalArgumentException <ul>
1480 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1481 * </ul>
1482 */
addVerifyListener(VerifyListener verifyListener)1483 public void addVerifyListener(VerifyListener verifyListener) {
1484 checkWidget();
1485 if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1486 addListener(SWT.Verify, new TypedListener(verifyListener));
1487 }
1488 /**
1489 * Adds a word movement listener. A movement event is sent when the boundary
1490 * of a word is needed. For example, this occurs during word next and word
1491 * previous actions.
1492 *
1493 * @param movementListener the listener
1494 * @exception SWTException <ul>
1495 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1496 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1497 * </ul>
1498 * @exception IllegalArgumentException <ul>
1499 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1500 * </ul>
1501 *
1502 * @see MovementEvent
1503 * @see MovementListener
1504 * @see #removeWordMovementListener
1505 *
1506 * @since 3.3
1507 */
addWordMovementListener(MovementListener movementListener)1508 public void addWordMovementListener(MovementListener movementListener) {
1509 checkWidget();
1510 if (movementListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1511 addListener(ST.WordNext, new StyledTextListener(movementListener));
1512 addListener(ST.WordPrevious, new StyledTextListener(movementListener));
1513 }
1514 /**
1515 * Appends a string to the text at the end of the widget.
1516 *
1517 * @param string the string to be appended
1518 * @see #replaceTextRange(int,int,String)
1519 * @exception SWTException <ul>
1520 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1521 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1522 * </ul>
1523 * @exception IllegalArgumentException <ul>
1524 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1525 * </ul>
1526 */
append(String string)1527 public void append(String string) {
1528 checkWidget();
1529 if (string == null) {
1530 SWT.error(SWT.ERROR_NULL_ARGUMENT);
1531 }
1532 int lastChar = Math.max(getCharCount(), 0);
1533 replaceTextRange(lastChar, 0, string);
1534 }
1535 /**
1536 * Calculates the scroll bars
1537 */
calculateScrollBars()1538 void calculateScrollBars() {
1539 ScrollBar horizontalBar = getHorizontalBar();
1540 ScrollBar verticalBar = getVerticalBar();
1541 setScrollBars(true);
1542 if (verticalBar != null) {
1543 verticalBar.setIncrement(getVerticalIncrement());
1544 }
1545 if (horizontalBar != null) {
1546 horizontalBar.setIncrement(getHorizontalIncrement());
1547 }
1548 }
1549 /**
1550 * Calculates the top index based on the current vertical scroll offset.
1551 * The top index is the index of the topmost fully visible line or the
1552 * topmost partially visible line if no line is fully visible.
1553 * The top index starts at 0.
1554 */
calculateTopIndex(int delta)1555 void calculateTopIndex(int delta) {
1556 int oldDelta = delta;
1557 int oldTopIndex = topIndex;
1558 int oldTopIndexY = topIndexY;
1559 if (isFixedLineHeight()) {
1560 int verticalIncrement = getVerticalIncrement();
1561 if (verticalIncrement == 0) {
1562 return;
1563 }
1564 topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement);
1565 // Set top index to partially visible top line if no line is fully
1566 // visible but at least some of the widget client area is visible.
1567 // Fixes bug 15088.
1568 if (topIndex > 0) {
1569 if (clientAreaHeight > 0) {
1570 int bottomPixel = getVerticalScrollOffset() + clientAreaHeight;
1571 int fullLineTopPixel = topIndex * verticalIncrement;
1572 int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
1573 // set top index to partially visible line if no line fully fits in
1574 // client area or if space is available but not used (the latter should
1575 // never happen because we use claimBottomFreeSpace)
1576 if (fullLineVisibleHeight < verticalIncrement) {
1577 topIndex = getVerticalScrollOffset() / verticalIncrement;
1578 }
1579 } else if (topIndex >= content.getLineCount()) {
1580 topIndex = content.getLineCount() - 1;
1581 }
1582 }
1583 } else {
1584 if (delta >= 0) {
1585 delta -= topIndexY;
1586 int lineIndex = topIndex;
1587 int lineCount = content.getLineCount();
1588 while (lineIndex < lineCount) {
1589 if (delta <= 0) break;
1590 delta -= renderer.getCachedLineHeight(lineIndex++);
1591 }
1592 if (lineIndex < lineCount && -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1593 topIndex = lineIndex;
1594 topIndexY = -delta;
1595 } else {
1596 topIndex = lineIndex - 1;
1597 topIndexY = -renderer.getCachedLineHeight(topIndex) - delta;
1598 }
1599 } else {
1600 delta -= topIndexY;
1601 int lineIndex = topIndex;
1602 while (lineIndex > 0) {
1603 int lineHeight = renderer.getCachedLineHeight(lineIndex - 1);
1604 if (delta + lineHeight > 0) break;
1605 delta += lineHeight;
1606 lineIndex--;
1607 }
1608 if (lineIndex == 0 || -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1609 topIndex = lineIndex;
1610 topIndexY = - delta;
1611 } else {
1612 topIndex = lineIndex - 1;
1613 topIndexY = - renderer.getCachedLineHeight(topIndex) - delta;
1614 }
1615 }
1616 }
1617 if (topIndex < 0) {
1618 // TODO: This logging is in place to determine why topIndex is getting set to negative values.
1619 // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
1620 System.err.println("StyledText: topIndex was " + topIndex
1621 + ", isFixedLineHeight() = " + isFixedLineHeight()
1622 + ", delta = " + delta
1623 + ", content.getLineCount() = " + content.getLineCount()
1624 + ", clientAreaHeight = " + clientAreaHeight
1625 + ", oldTopIndex = " + oldTopIndex
1626 + ", oldTopIndexY = " + oldTopIndexY
1627 + ", getVerticalScrollOffset = " + getVerticalScrollOffset()
1628 + ", oldDelta = " + oldDelta
1629 + ", getVerticalIncrement() = " + getVerticalIncrement());
1630 topIndex = 0;
1631 }
1632 if (topIndex != oldTopIndex || oldTopIndexY != topIndexY) {
1633 int width = renderer.getWidth();
1634 renderer.calculateClientArea();
1635 if (width != renderer.getWidth()) {
1636 setScrollBars(false);
1637 }
1638 }
1639 }
1640 /**
1641 * Hides the scroll bars if widget is created in single line mode.
1642 */
checkStyle(int style)1643 static int checkStyle(int style) {
1644 if ((style & SWT.SINGLE) != 0) {
1645 style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI);
1646 } else {
1647 style |= SWT.MULTI;
1648 if ((style & SWT.WRAP) != 0) {
1649 style &= ~SWT.H_SCROLL;
1650 }
1651 }
1652 style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND;
1653 /* Clear SWT.CENTER to avoid the conflict with SWT.EMBEDDED */
1654 return style & ~SWT.CENTER;
1655 }
1656 /**
1657 * Scrolls down the text to use new space made available by a resize or by
1658 * deleted lines.
1659 */
claimBottomFreeSpace()1660 void claimBottomFreeSpace() {
1661 if (ime.getCompositionOffset() != -1) return;
1662 if (isFixedLineHeight()) {
1663 int newVerticalOffset = Math.max(0, renderer.getHeight() - clientAreaHeight);
1664 if (newVerticalOffset < getVerticalScrollOffset()) {
1665 scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
1666 }
1667 } else {
1668 int bottomIndex = getPartialBottomIndex();
1669 int height = getLinePixel(bottomIndex + 1);
1670 if (clientAreaHeight > height) {
1671 scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
1672 }
1673 }
1674 }
1675 /**
1676 * Scrolls text to the right to use new space made available by a resize.
1677 */
claimRightFreeSpace()1678 void claimRightFreeSpace() {
1679 int newHorizontalOffset = Math.max(0, renderer.getWidth() - clientAreaWidth);
1680 if (newHorizontalOffset < horizontalScrollOffset) {
1681 // item is no longer drawn past the right border of the client area
1682 // align the right end of the item with the right border of the
1683 // client area (window is scrolled right).
1684 scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
1685 }
1686 }
clearBlockSelection(boolean reset, boolean sendEvent)1687 void clearBlockSelection(boolean reset, boolean sendEvent) {
1688 if (reset) resetSelection();
1689 blockXAnchor = blockYAnchor = -1;
1690 blockXLocation = blockYLocation = -1;
1691 caretDirection = SWT.NULL;
1692 updateCaretVisibility();
1693 super.redraw();
1694 if (sendEvent) sendSelectionEvent();
1695 }
1696 /**
1697 * Removes the widget selection.
1698 *
1699 * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
1700 */
clearSelection(boolean sendEvent)1701 void clearSelection(boolean sendEvent) {
1702 int selectionStart = selection.x;
1703 int selectionEnd = selection.y;
1704 resetSelection();
1705 // redraw old selection, if any
1706 if (selectionEnd - selectionStart > 0) {
1707 int length = content.getCharCount();
1708 // called internally to remove selection after text is removed
1709 // therefore make sure redraw range is valid.
1710 int redrawStart = Math.min(selectionStart, length);
1711 int redrawEnd = Math.min(selectionEnd, length);
1712 if (redrawEnd - redrawStart > 0) {
1713 internalRedrawRange(redrawStart, redrawEnd - redrawStart);
1714 }
1715 if (sendEvent) {
1716 sendSelectionEvent();
1717 }
1718 }
1719 }
1720 @Override
computeSize(int wHint, int hHint, boolean changed)1721 public Point computeSize (int wHint, int hHint, boolean changed) {
1722 checkWidget();
1723 int lineCount = (getStyle() & SWT.SINGLE) != 0 ? 1 : content.getLineCount();
1724 int width = 0;
1725 int height = 0;
1726 if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
1727 Display display = getDisplay();
1728 int maxHeight = display.getClientArea().height;
1729 for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
1730 TextLayout layout = renderer.getTextLayout(lineIndex);
1731 int wrapWidth = layout.getWidth();
1732 if (wordWrap) layout.setWidth(wHint == 0 ? 1 : wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(1, wHint - leftMargin - rightMargin));
1733 Rectangle rect = layout.getBounds();
1734 height += rect.height;
1735 width = Math.max(width, rect.width);
1736 layout.setWidth(wrapWidth);
1737 renderer.disposeTextLayout(layout);
1738 if (isFixedLineHeight() && height > maxHeight) break;
1739 }
1740 if (isFixedLineHeight()) {
1741 height = lineCount * renderer.getLineHeight();
1742 }
1743 }
1744 // Use default values if no text is defined.
1745 if (width == 0) width = DEFAULT_WIDTH;
1746 if (height == 0) height = DEFAULT_HEIGHT;
1747 if (wHint != SWT.DEFAULT) width = wHint;
1748 if (hHint != SWT.DEFAULT) height = hHint;
1749 int wTrim = getLeftMargin() + rightMargin + getCaretWidth();
1750 int hTrim = topMargin + bottomMargin;
1751 Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
1752 return new Point (rect.width, rect.height);
1753 }
1754 /**
1755 * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
1756 * <p>
1757 * The text will be put on the clipboard in plain text format and RTF format.
1758 * The <code>DND.CLIPBOARD</code> clipboard is used for data that is
1759 * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
1760 * by menu action.
1761 * </p>
1762 *
1763 * @exception SWTException <ul>
1764 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1765 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1766 * </ul>
1767 */
copy()1768 public void copy() {
1769 checkWidget();
1770 copySelection(DND.CLIPBOARD);
1771 }
1772 /**
1773 * Copies the selected text to the specified clipboard. The text will be put in the
1774 * clipboard in plain text format and RTF format.
1775 * <p>
1776 * The clipboardType is one of the clipboard constants defined in class
1777 * <code>DND</code>. The <code>DND.CLIPBOARD</code> clipboard is
1778 * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
1779 * or by menu action. The <code>DND.SELECTION_CLIPBOARD</code>
1780 * clipboard is used for data that is transferred by selecting text and pasting
1781 * with the middle mouse button.
1782 * </p>
1783 *
1784 * @param clipboardType indicates the type of clipboard
1785 *
1786 * @exception SWTException <ul>
1787 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1788 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1789 * </ul>
1790 *
1791 * @since 3.1
1792 */
copy(int clipboardType)1793 public void copy(int clipboardType) {
1794 checkWidget();
1795 copySelection(clipboardType);
1796 }
copySelection(int type)1797 boolean copySelection(int type) {
1798 if (type != DND.CLIPBOARD && type != DND.SELECTION_CLIPBOARD) return false;
1799 try {
1800 if (blockSelection && blockXLocation != -1) {
1801 String text = getBlockSelectionText(PlatformLineDelimiter);
1802 if (text.length() > 0) {
1803 //TODO RTF support
1804 TextTransfer plainTextTransfer = TextTransfer.getInstance();
1805 Object[] data = new Object[]{text};
1806 Transfer[] types = new Transfer[]{plainTextTransfer};
1807 clipboard.setContents(data, types, type);
1808 return true;
1809 }
1810 } else {
1811 int length = selection.y - selection.x;
1812 if (length > 0) {
1813 setClipboardContent(selection.x, length, type);
1814 return true;
1815 }
1816 }
1817 } catch (SWTError error) {
1818 // Copy to clipboard failed. This happens when another application
1819 // is accessing the clipboard while we copy. Ignore the error.
1820 // Rethrow all other errors. Fixes bug 17578.
1821 if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
1822 throw error;
1823 }
1824 }
1825 return false;
1826 }
1827 /**
1828 * Returns the alignment of the widget.
1829 *
1830 * @return the alignment
1831 *
1832 * @exception SWTException <ul>
1833 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1834 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1835 * </ul>
1836 *
1837 * @see #getLineAlignment(int)
1838 *
1839 * @since 3.2
1840 */
getAlignment()1841 public int getAlignment() {
1842 checkWidget();
1843 return alignment;
1844 }
1845 /**
1846 * Returns the Always Show Scrollbars flag. True if the scrollbars are
1847 * always shown even if they are not required. False if the scrollbars are only
1848 * visible when some part of the content needs to be scrolled to be seen.
1849 * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
1850 * horizontal and vertical directions.
1851 *
1852 * @return the Always Show Scrollbars flag value
1853 *
1854 * @exception SWTException <ul>
1855 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1856 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1857 * </ul>
1858 *
1859 * @since 3.8
1860 */
getAlwaysShowScrollBars()1861 public boolean getAlwaysShowScrollBars() {
1862 checkWidget();
1863 return alwaysShowScroll;
1864 }
getAvailableHeightAbove(int height)1865 int getAvailableHeightAbove(int height) {
1866 int maxHeight = verticalScrollOffset;
1867 if (maxHeight == -1) {
1868 int lineIndex = topIndex - 1;
1869 maxHeight = -topIndexY;
1870 if (topIndexY > 0) {
1871 maxHeight += renderer.getLineHeight(lineIndex--);
1872 }
1873 while (height > maxHeight && lineIndex >= 0) {
1874 maxHeight += renderer.getLineHeight(lineIndex--);
1875 }
1876 }
1877 return Math.min(height, maxHeight);
1878 }
getAvailableHeightBellow(int height)1879 int getAvailableHeightBellow(int height) {
1880 int partialBottomIndex = getPartialBottomIndex();
1881 int topY = getLinePixel(partialBottomIndex);
1882 int lineHeight = renderer.getLineHeight(partialBottomIndex);
1883 int availableHeight = 0;
1884 int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
1885 if (topY + lineHeight > clientAreaHeight) {
1886 availableHeight = lineHeight - (clientAreaHeight - topY);
1887 }
1888 int lineIndex = partialBottomIndex + 1;
1889 int lineCount = content.getLineCount();
1890 while (height > availableHeight && lineIndex < lineCount) {
1891 availableHeight += renderer.getLineHeight(lineIndex++);
1892 }
1893 return Math.min(height, availableHeight);
1894 }
1895 /**
1896 * Returns the color of the margins.
1897 *
1898 * @return the color of the margins.
1899 * @exception SWTException <ul>
1900 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1901 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1902 * </ul>
1903 *
1904 * @since 3.5
1905 */
getMarginColor()1906 public Color getMarginColor() {
1907 checkWidget();
1908 return marginColor != null ? marginColor : getBackground();
1909 }
1910 /**
1911 * Returns a string that uses only the line delimiter specified by the
1912 * StyledTextContent implementation.
1913 * <p>
1914 * Returns only the first line if the widget has the SWT.SINGLE style.
1915 * </p>
1916 *
1917 * @param text the text that may have line delimiters that don't
1918 * match the model line delimiter. Possible line delimiters
1919 * are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
1920 * @return the converted text that only uses the line delimiter
1921 * specified by the model. Returns only the first line if the widget
1922 * has the SWT.SINGLE style.
1923 */
getModelDelimitedText(String text)1924 String getModelDelimitedText(String text) {
1925 int length = text.length();
1926 if (length == 0) {
1927 return text;
1928 }
1929 int crIndex = 0;
1930 int lfIndex = 0;
1931 int i = 0;
1932 StringBuilder convertedText = new StringBuilder(length);
1933 String delimiter = getLineDelimiter();
1934 while (i < length) {
1935 if (crIndex != -1) {
1936 crIndex = text.indexOf(SWT.CR, i);
1937 }
1938 if (lfIndex != -1) {
1939 lfIndex = text.indexOf(SWT.LF, i);
1940 }
1941 if (lfIndex == -1 && crIndex == -1) { // no more line breaks?
1942 break;
1943 } else if ((crIndex < lfIndex && crIndex != -1) || lfIndex == -1) {
1944 convertedText.append(text.substring(i, crIndex));
1945 if (lfIndex == crIndex + 1) { // CR/LF combination?
1946 i = lfIndex + 1;
1947 } else {
1948 i = crIndex + 1;
1949 }
1950 } else { // LF occurs before CR!
1951 convertedText.append(text.substring(i, lfIndex));
1952 i = lfIndex + 1;
1953 }
1954 if (isSingleLine()) {
1955 break;
1956 }
1957 convertedText.append(delimiter);
1958 }
1959 // copy remaining text if any and if not in single line mode or no
1960 // text copied thus far (because there only is one line)
1961 if (i < length && (!isSingleLine() || convertedText.length() == 0)) {
1962 convertedText.append(text.substring(i));
1963 }
1964 return convertedText.toString();
1965 }
checkDragDetect(Event event)1966 boolean checkDragDetect(Event event) {
1967 if (!isListening(SWT.DragDetect)) return false;
1968 if (event.button != 1) return false;
1969 if (blockSelection && blockXLocation != -1) {
1970 Rectangle rect = getBlockSelectionRectangle();
1971 if (rect.contains(event.x, event.y)) {
1972 return dragDetect(event);
1973 }
1974 } else {
1975 if (selection.x == selection.y) return false;
1976 int offset = getOffsetAtPoint(event.x, event.y, null, true);
1977 if (selection.x <= offset && offset < selection.y) {
1978 return dragDetect(event);
1979 }
1980
1981 }
1982 return false;
1983 }
1984
1985 /**
1986 * Creates default key bindings.
1987 */
createKeyBindings()1988 void createKeyBindings() {
1989 int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT;
1990 int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
1991
1992 // Navigation
1993 setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
1994 setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
1995 if (IS_MAC) {
1996 setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START);
1997 setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END);
1998 setKeyBinding(SWT.HOME, ST.TEXT_START);
1999 setKeyBinding(SWT.END, ST.TEXT_END);
2000 setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START);
2001 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END);
2002 setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT);
2003 setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS);
2004 } else {
2005 setKeyBinding(SWT.HOME, ST.LINE_START);
2006 setKeyBinding(SWT.END, ST.LINE_END);
2007 setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
2008 setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
2009 setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
2010 setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
2011 }
2012 setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
2013 setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
2014 setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START);
2015 setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END);
2016 setKeyBinding(nextKey, ST.COLUMN_NEXT);
2017 setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
2018
2019 // Selection
2020 setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
2021 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
2022 if (IS_MAC) {
2023 setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START);
2024 setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END);
2025 setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START);
2026 setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END);
2027 setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2028 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2029 setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT);
2030 setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS);
2031 } else {
2032 setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
2033 setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
2034 setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2035 setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2036 setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
2037 setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
2038 }
2039 setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
2040 setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
2041 setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START);
2042 setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END);
2043 setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT);
2044 setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
2045
2046 // Modification
2047 // Cut, Copy, Paste
2048 setKeyBinding('X' | SWT.MOD1, ST.CUT);
2049 setKeyBinding('C' | SWT.MOD1, ST.COPY);
2050 setKeyBinding('V' | SWT.MOD1, ST.PASTE);
2051 if (IS_MAC) {
2052 setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT);
2053 setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS);
2054 setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT);
2055 } else {
2056 // Cut, Copy, Paste Wordstar style
2057 setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT);
2058 setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY);
2059 setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE);
2060 }
2061 setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS);
2062 setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
2063 setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
2064 setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS);
2065 setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT);
2066
2067 // Miscellaneous
2068 setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
2069 }
2070 /**
2071 * Create the bitmaps to use for the caret in bidi mode. This
2072 * method only needs to be called upon widget creation and when the
2073 * font changes (the caret bitmap height needs to match font height).
2074 */
createCaretBitmaps()2075 void createCaretBitmaps() {
2076 int caretWidth = BIDI_CARET_WIDTH;
2077 Display display = getDisplay();
2078 if (leftCaretBitmap != null) {
2079 if (defaultCaret != null && leftCaretBitmap.equals(defaultCaret.getImage())) {
2080 defaultCaret.setImage(null);
2081 }
2082 leftCaretBitmap.dispose();
2083 }
2084 int lineHeight = renderer.getLineHeight();
2085 leftCaretBitmap = new Image(display, caretWidth, lineHeight);
2086 GC gc = new GC (leftCaretBitmap);
2087 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2088 gc.fillRectangle(0, 0, caretWidth, lineHeight);
2089 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2090 gc.drawLine(0,0,0,lineHeight);
2091 gc.drawLine(0,0,caretWidth-1,0);
2092 gc.drawLine(0,1,1,1);
2093 gc.dispose();
2094
2095 if (rightCaretBitmap != null) {
2096 if (defaultCaret != null && rightCaretBitmap.equals(defaultCaret.getImage())) {
2097 defaultCaret.setImage(null);
2098 }
2099 rightCaretBitmap.dispose();
2100 }
2101 rightCaretBitmap = new Image(display, caretWidth, lineHeight);
2102 gc = new GC (rightCaretBitmap);
2103 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2104 gc.fillRectangle(0, 0, caretWidth, lineHeight);
2105 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2106 gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight);
2107 gc.drawLine(0,0,caretWidth-1,0);
2108 gc.drawLine(caretWidth-1,1,1,1);
2109 gc.dispose();
2110 }
2111 /**
2112 * Moves the selected text to the clipboard. The text will be put in the
2113 * clipboard in plain text format and RTF format.
2114 *
2115 * @exception SWTException <ul>
2116 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2117 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2118 * </ul>
2119 */
cut()2120 public void cut() {
2121 checkWidget();
2122 // Abort cut operation if copy to clipboard fails.
2123 // Fixes bug 21030.
2124 if (copySelection(DND.CLIPBOARD)) {
2125 if (blockSelection && blockXLocation != -1) {
2126 insertBlockSelectionText((char)0, SWT.NULL);
2127 } else {
2128 doDelete();
2129 }
2130 }
2131 }
2132 /**
2133 * A mouse move event has occurred. See if we should start autoscrolling. If
2134 * the move position is outside of the client area, initiate autoscrolling.
2135 * Otherwise, we've moved back into the widget so end autoscrolling.
2136 */
doAutoScroll(Event event)2137 void doAutoScroll(Event event) {
2138 int caretLine = getCaretLine();
2139 if (event.y > clientAreaHeight - bottomMargin && caretLine != content.getLineCount() - 1) {
2140 doAutoScroll(SWT.DOWN, event.y - (clientAreaHeight - bottomMargin));
2141 } else if (event.y < topMargin && caretLine != 0) {
2142 doAutoScroll(SWT.UP, topMargin - event.y);
2143 } else if (event.x < leftMargin && !wordWrap) {
2144 doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
2145 } else if (event.x > clientAreaWidth - rightMargin && !wordWrap) {
2146 doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - rightMargin));
2147 } else {
2148 endAutoScroll();
2149 }
2150 }
2151 /**
2152 * Initiates autoscrolling.
2153 *
2154 * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
2155 */
doAutoScroll(int direction, int distance)2156 void doAutoScroll(int direction, int distance) {
2157 autoScrollDistance = distance;
2158 // If we're already autoscrolling in the given direction do nothing
2159 if (autoScrollDirection == direction) {
2160 return;
2161 }
2162
2163 Runnable timer = null;
2164 final Display display = getDisplay();
2165 // Set a timer that will simulate the user pressing and holding
2166 // down a cursor key (i.e., arrowUp, arrowDown).
2167 if (direction == SWT.UP) {
2168 timer = new Runnable() {
2169 @Override
2170 public void run() {
2171 /* Bug 437357 - NPE in StyledText.getCaretLine
2172 * StyledText.content is null at times, probably because the
2173 * widget itself has been disposed.
2174 */
2175 if (isDisposed()) return;
2176 if (autoScrollDirection == SWT.UP) {
2177 if (blockSelection) {
2178 int verticalScrollOffset = getVerticalScrollOffset();
2179 int y = blockYLocation - verticalScrollOffset;
2180 int pixels = Math.max(-autoScrollDistance, -verticalScrollOffset);
2181 if (pixels != 0) {
2182 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
2183 scrollVertical(pixels, true);
2184 }
2185 } else {
2186 doSelectionPageUp(autoScrollDistance);
2187 }
2188 display.timerExec(V_SCROLL_RATE, this);
2189 }
2190 }
2191 };
2192 autoScrollDirection = direction;
2193 display.timerExec(V_SCROLL_RATE, timer);
2194 } else if (direction == SWT.DOWN) {
2195 timer = new Runnable() {
2196 @Override
2197 public void run() {
2198 /* Bug 437357 - NPE in StyledText.getCaretLine
2199 * StyledText.content is null at times, probably because the
2200 * widget itself has been disposed.
2201 */
2202 if (isDisposed()) return;
2203 if (autoScrollDirection == SWT.DOWN) {
2204 if (blockSelection) {
2205 int verticalScrollOffset = getVerticalScrollOffset();
2206 int y = blockYLocation - verticalScrollOffset;
2207 int max = renderer.getHeight() - verticalScrollOffset - clientAreaHeight;
2208 int pixels = Math.min(autoScrollDistance, Math.max(0,max));
2209 if (pixels != 0) {
2210 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
2211 scrollVertical(pixels, true);
2212 }
2213 } else {
2214 doSelectionPageDown(autoScrollDistance);
2215 }
2216 display.timerExec(V_SCROLL_RATE, this);
2217 }
2218 }
2219 };
2220 autoScrollDirection = direction;
2221 display.timerExec(V_SCROLL_RATE, timer);
2222 } else if (direction == ST.COLUMN_NEXT) {
2223 timer = new Runnable() {
2224 @Override
2225 public void run() {
2226 /* Bug 437357 - NPE in StyledText.getCaretLine
2227 * StyledText.content is null at times, probably because the
2228 * widget itself has been disposed.
2229 */
2230 if (isDisposed()) return;
2231 if (autoScrollDirection == ST.COLUMN_NEXT) {
2232 if (blockSelection) {
2233 int x = blockXLocation - horizontalScrollOffset;
2234 int max = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
2235 int pixels = Math.min(autoScrollDistance, Math.max(0,max));
2236 if (pixels != 0) {
2237 setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
2238 scrollHorizontal(pixels, true);
2239 }
2240 } else {
2241 doVisualNext();
2242 setMouseWordSelectionAnchor();
2243 doMouseSelection();
2244 }
2245 display.timerExec(H_SCROLL_RATE, this);
2246 }
2247 }
2248 };
2249 autoScrollDirection = direction;
2250 display.timerExec(H_SCROLL_RATE, timer);
2251 } else if (direction == ST.COLUMN_PREVIOUS) {
2252 timer = new Runnable() {
2253 @Override
2254 public void run() {
2255 /* Bug 437357 - NPE in StyledText.getCaretLine
2256 * StyledText.content is null at times, probably because the
2257 * widget itself has been disposed.
2258 */
2259 if (isDisposed()) return;
2260 if (autoScrollDirection == ST.COLUMN_PREVIOUS) {
2261 if (blockSelection) {
2262 int x = blockXLocation - horizontalScrollOffset;
2263 int pixels = Math.max(-autoScrollDistance, -horizontalScrollOffset);
2264 if (pixels != 0) {
2265 setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
2266 scrollHorizontal(pixels, true);
2267 }
2268 } else {
2269 doVisualPrevious();
2270 setMouseWordSelectionAnchor();
2271 doMouseSelection();
2272 }
2273 display.timerExec(H_SCROLL_RATE, this);
2274 }
2275 }
2276 };
2277 autoScrollDirection = direction;
2278 display.timerExec(H_SCROLL_RATE, timer);
2279 }
2280 }
2281 /**
2282 * Deletes the previous character. Delete the selected text if any.
2283 * Move the caret in front of the deleted text.
2284 */
doBackspace()2285 void doBackspace() {
2286 Event event = new Event();
2287 event.text = "";
2288 if (selection.x != selection.y) {
2289 event.start = selection.x;
2290 event.end = selection.y;
2291 sendKeyEvent(event);
2292 } else if (caretOffset > 0) {
2293 int lineIndex = content.getLineAtOffset(caretOffset);
2294 int lineOffset = content.getOffsetAtLine(lineIndex);
2295 if (caretOffset == lineOffset) {
2296 lineOffset = content.getOffsetAtLine(lineIndex - 1);
2297 event.start = lineOffset + content.getLine(lineIndex - 1).length();
2298 event.end = caretOffset;
2299 } else {
2300 boolean isSurrogate = false;
2301 String lineText = content.getLine(lineIndex);
2302 char ch = lineText.charAt(caretOffset - lineOffset - 1);
2303 if (0xDC00 <= ch && ch <= 0xDFFF) {
2304 if (caretOffset - lineOffset - 2 >= 0) {
2305 ch = lineText.charAt(caretOffset - lineOffset - 2);
2306 isSurrogate = 0xD800 <= ch && ch <= 0xDBFF;
2307 }
2308 }
2309 TextLayout layout = renderer.getTextLayout(lineIndex);
2310 int start = layout.getPreviousOffset(caretOffset - lineOffset, isSurrogate ? SWT.MOVEMENT_CLUSTER : SWT.MOVEMENT_CHAR);
2311 renderer.disposeTextLayout(layout);
2312 event.start = start + lineOffset;
2313 event.end = caretOffset;
2314 }
2315 sendKeyEvent(event);
2316 }
2317 }
doBlockColumn(boolean next)2318 void doBlockColumn(boolean next) {
2319 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2320 int x = blockXLocation - horizontalScrollOffset;
2321 int y = blockYLocation - getVerticalScrollOffset();
2322 int[] trailing = new int[1];
2323 int offset = getOffsetAtPoint(x, y, trailing, true);
2324 if (offset != -1) {
2325 offset += trailing[0];
2326 int lineIndex = content.getLineAtOffset(offset);
2327 int newOffset;
2328 if (next) {
2329 newOffset = getClusterNext(offset, lineIndex);
2330 } else {
2331 newOffset = getClusterPrevious(offset, lineIndex);
2332 }
2333 offset = newOffset != offset ? newOffset : -1;
2334 }
2335 if (offset != -1) {
2336 setBlockSelectionOffset(offset, true);
2337 showCaret();
2338 } else {
2339 int width = next ? renderer.averageCharWidth : -renderer.averageCharWidth;
2340 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2341 x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
2342 setBlockSelectionLocation(x, y, true);
2343 Rectangle rect = new Rectangle(x, y, 0, 0);
2344 showLocation(rect, true);
2345 }
2346 }
doBlockContentStartEnd(boolean end)2347 void doBlockContentStartEnd(boolean end) {
2348 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2349 int offset = end ? content.getCharCount() : 0;
2350 setBlockSelectionOffset(offset, true);
2351 showCaret();
2352 }
doBlockWord(boolean next)2353 void doBlockWord(boolean next) {
2354 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2355 int x = blockXLocation - horizontalScrollOffset;
2356 int y = blockYLocation - getVerticalScrollOffset();
2357 int[] trailing = new int[1];
2358 int offset = getOffsetAtPoint(x, y, trailing, true);
2359 if (offset != -1) {
2360 offset += trailing[0];
2361 int lineIndex = content.getLineAtOffset(offset);
2362 int lineOffset = content.getOffsetAtLine(lineIndex);
2363 String lineText = content.getLine(lineIndex);
2364 int lineLength = lineText.length();
2365 int newOffset = offset;
2366 if (next) {
2367 if (offset < lineOffset + lineLength) {
2368 newOffset = getWordNext(offset, SWT.MOVEMENT_WORD);
2369 }
2370 } else {
2371 if (offset > lineOffset) {
2372 newOffset = getWordPrevious(offset, SWT.MOVEMENT_WORD);
2373 }
2374 }
2375 offset = newOffset != offset ? newOffset : -1;
2376 }
2377 if (offset != -1) {
2378 setBlockSelectionOffset(offset, true);
2379 showCaret();
2380 } else {
2381 int width = (next ? renderer.averageCharWidth : -renderer.averageCharWidth) * 6;
2382 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2383 x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
2384 setBlockSelectionLocation(x, y, true);
2385 Rectangle rect = new Rectangle(x, y, 0, 0);
2386 showLocation(rect, true);
2387 }
2388 }
doBlockLineVertical(boolean up)2389 void doBlockLineVertical(boolean up) {
2390 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2391 int y = blockYLocation - getVerticalScrollOffset();
2392 int lineIndex = getLineIndex(y);
2393 if (up) {
2394 if (lineIndex > 0) {
2395 y = getLinePixel(lineIndex - 1);
2396 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
2397 if (y < topMargin) {
2398 scrollVertical(y - topMargin, true);
2399 }
2400 }
2401 } else {
2402 int lineCount = content.getLineCount();
2403 if (lineIndex + 1 < lineCount) {
2404 y = getLinePixel(lineIndex + 2) - 1;
2405 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
2406 int bottom = clientAreaHeight - bottomMargin;
2407 if (y > bottom) {
2408 scrollVertical(y - bottom, true);
2409 }
2410 }
2411 }
2412 }
doBlockLineHorizontal(boolean end)2413 void doBlockLineHorizontal(boolean end) {
2414 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2415 int x = blockXLocation - horizontalScrollOffset;
2416 int y = blockYLocation - getVerticalScrollOffset();
2417 int lineIndex = getLineIndex(y);
2418 int lineOffset = content.getOffsetAtLine(lineIndex);
2419 String lineText = content.getLine(lineIndex);
2420 int lineLength = lineText.length();
2421 int[] trailing = new int[1];
2422 int offset = getOffsetAtPoint(x, y, trailing, true);
2423 if (offset != -1) {
2424 offset += trailing[0];
2425 int newOffset = offset;
2426 if (end) {
2427 if (offset < lineOffset + lineLength) {
2428 newOffset = lineOffset + lineLength;
2429 }
2430 } else {
2431 if (offset > lineOffset) {
2432 newOffset = lineOffset;
2433 }
2434 }
2435 offset = newOffset != offset ? newOffset : -1;
2436 } else {
2437 if (!end) offset = lineOffset + lineLength;
2438 }
2439 if (offset != -1) {
2440 setBlockSelectionOffset(offset, true);
2441 showCaret();
2442 } else {
2443 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2444 x = (end ? maxWidth : 0) - horizontalScrollOffset;
2445 setBlockSelectionLocation(x, y, true);
2446 Rectangle rect = new Rectangle(x, y, 0, 0);
2447 showLocation(rect, true);
2448 }
2449 }
doBlockSelection(boolean sendEvent)2450 void doBlockSelection(boolean sendEvent) {
2451 if (caretOffset > selectionAnchor) {
2452 selection.x = selectionAnchor;
2453 selection.y = caretOffset;
2454 } else {
2455 selection.x = caretOffset;
2456 selection.y = selectionAnchor;
2457 }
2458 updateCaretVisibility();
2459 setCaretLocation();
2460 super.redraw();
2461 if (sendEvent) {
2462 sendSelectionEvent();
2463 }
2464 sendAccessibleTextCaretMoved();
2465 }
2466 /**
2467 * Replaces the selection with the character or insert the character at the
2468 * current caret position if no selection exists.
2469 * <p>
2470 * If a carriage return was typed replace it with the line break character
2471 * used by the widget on this platform.
2472 * </p>
2473 *
2474 * @param key the character typed by the user
2475 */
doContent(char key)2476 void doContent(char key) {
2477 if (blockSelection && blockXLocation != -1) {
2478 insertBlockSelectionText(key, SWT.NULL);
2479 return;
2480 }
2481
2482 Event event = new Event();
2483 event.start = selection.x;
2484 event.end = selection.y;
2485 // replace a CR line break with the widget line break
2486 // CR does not make sense on Windows since most (all?) applications
2487 // don't recognize CR as a line break.
2488 if (key == SWT.CR || key == SWT.LF) {
2489 if (!isSingleLine()) {
2490 event.text = getLineDelimiter();
2491 }
2492 } else if (selection.x == selection.y && overwrite && key != TAB) {
2493 // no selection and overwrite mode is on and the typed key is not a
2494 // tab character (tabs are always inserted without overwriting)?
2495 int lineIndex = content.getLineAtOffset(event.end);
2496 int lineOffset = content.getOffsetAtLine(lineIndex);
2497 String line = content.getLine(lineIndex);
2498 // replace character at caret offset if the caret is not at the
2499 // end of the line
2500 if (event.end < lineOffset + line.length()) {
2501 event.end++;
2502 }
2503 event.text = new String(new char[] {key});
2504 } else {
2505 event.text = new String(new char[] {key});
2506 }
2507 if (event.text != null) {
2508 if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
2509 return;
2510 }
2511 sendKeyEvent(event);
2512 }
2513 }
2514 /**
2515 * Moves the caret after the last character of the widget content.
2516 */
doContentEnd()2517 void doContentEnd() {
2518 // place caret at end of first line if receiver is in single
2519 // line mode. fixes 4820.
2520 if (isSingleLine()) {
2521 doLineEnd();
2522 } else {
2523 int length = content.getCharCount();
2524 setCaretOffset(length, SWT.DEFAULT);
2525 showCaret();
2526 }
2527 }
2528 /**
2529 * Moves the caret in front of the first character of the widget content.
2530 */
doContentStart()2531 void doContentStart() {
2532 setCaretOffset(0, SWT.DEFAULT);
2533 showCaret();
2534 }
2535 /**
2536 * Moves the caret to the start of the selection if a selection exists.
2537 * Otherwise, if no selection exists move the cursor according to the
2538 * cursor selection rules.
2539 *
2540 * @see #doSelectionCursorPrevious
2541 */
doCursorPrevious()2542 void doCursorPrevious() {
2543 if (selection.y - selection.x > 0) {
2544 setCaretOffset(selection.x, OFFSET_LEADING);
2545 showCaret();
2546 } else {
2547 doSelectionCursorPrevious();
2548 }
2549 }
2550 /**
2551 * Moves the caret to the end of the selection if a selection exists.
2552 * Otherwise, if no selection exists move the cursor according to the
2553 * cursor selection rules.
2554 *
2555 * @see #doSelectionCursorNext
2556 */
doCursorNext()2557 void doCursorNext() {
2558 if (selection.y - selection.x > 0) {
2559 setCaretOffset(selection.y, PREVIOUS_OFFSET_TRAILING);
2560 showCaret();
2561 } else {
2562 doSelectionCursorNext();
2563 }
2564 }
2565 /**
2566 * Deletes the next character. Delete the selected text if any.
2567 */
doDelete()2568 void doDelete() {
2569 Event event = new Event();
2570 event.text = "";
2571 if (selection.x != selection.y) {
2572 event.start = selection.x;
2573 event.end = selection.y;
2574 sendKeyEvent(event);
2575 } else if (caretOffset < content.getCharCount()) {
2576 int line = content.getLineAtOffset(caretOffset);
2577 int lineOffset = content.getOffsetAtLine(line);
2578 int lineLength = content.getLine(line).length();
2579 if (caretOffset == lineOffset + lineLength) {
2580 event.start = caretOffset;
2581 event.end = content.getOffsetAtLine(line + 1);
2582 } else {
2583 event.start = caretOffset;
2584 event.end = getClusterNext(caretOffset, line);
2585 }
2586 sendKeyEvent(event);
2587 }
2588 }
2589 /**
2590 * Deletes the next word.
2591 */
doDeleteWordNext()2592 void doDeleteWordNext() {
2593 if (selection.x != selection.y) {
2594 // if a selection exists, treat the as if
2595 // only the delete key was pressed
2596 doDelete();
2597 } else {
2598 Event event = new Event();
2599 event.text = "";
2600 event.start = caretOffset;
2601 event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
2602 sendKeyEvent(event);
2603 }
2604 }
2605 /**
2606 * Deletes the previous word.
2607 */
doDeleteWordPrevious()2608 void doDeleteWordPrevious() {
2609 if (selection.x != selection.y) {
2610 // if a selection exists, treat as if
2611 // only the backspace key was pressed
2612 doBackspace();
2613 } else {
2614 Event event = new Event();
2615 event.text = "";
2616 event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
2617 event.end = caretOffset;
2618 sendKeyEvent(event);
2619 }
2620 }
2621 /**
2622 * Moves the caret one line down and to the same character offset relative
2623 * to the beginning of the line. Move the caret to the end of the new line
2624 * if the new line is shorter than the character offset. Moves the caret to
2625 * the end of the text if the caret already is on the last line.
2626 */
doLineDown(boolean select)2627 void doLineDown(boolean select) {
2628 int caretLine = getCaretLine();
2629 int lineCount = content.getLineCount();
2630 int y = 0;
2631 boolean lastLine = false;
2632 if (isWordWrap()) {
2633 int lineOffset = content.getOffsetAtLine(caretLine);
2634 int offsetInLine = caretOffset - lineOffset;
2635 TextLayout layout = renderer.getTextLayout(caretLine);
2636 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2637 int layoutLineCount = layout.getLineCount();
2638 if (lineIndex == layoutLineCount - 1) {
2639 lastLine = caretLine == lineCount - 1;
2640 caretLine++;
2641 } else {
2642 y = layout.getLineBounds(lineIndex + 1).y;
2643 y++; // bug 485722: workaround for fractional line heights
2644 }
2645 renderer.disposeTextLayout(layout);
2646 } else {
2647 lastLine = caretLine == lineCount - 1;
2648 caretLine++;
2649 }
2650 if (lastLine) {
2651 setCaretOffset(content.getCharCount(), SWT.DEFAULT);
2652 } else {
2653 int[] alignment = new int[1];
2654 int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
2655 setCaretOffset(offset, alignment[0]);
2656 }
2657 int oldColumnX = columnX;
2658 int oldHScrollOffset = horizontalScrollOffset;
2659 if (select) {
2660 setMouseWordSelectionAnchor();
2661 // select first and then scroll to reduce flash when key
2662 // repeat scrolls lots of lines
2663 doSelection(ST.COLUMN_NEXT);
2664 }
2665 showCaret();
2666 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2667 columnX = oldColumnX + hScrollChange;
2668 }
2669 /**
2670 * Moves the caret to the end of the line.
2671 */
doLineEnd()2672 void doLineEnd() {
2673 int caretLine = getCaretLine();
2674 int lineOffset = content.getOffsetAtLine(caretLine);
2675 int lineEndOffset;
2676 if (isWordWrap()) {
2677 TextLayout layout = renderer.getTextLayout(caretLine);
2678 int offsetInLine = caretOffset - lineOffset;
2679 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2680 int[] offsets = layout.getLineOffsets();
2681 lineEndOffset = lineOffset + offsets[lineIndex + 1];
2682 renderer.disposeTextLayout(layout);
2683 } else {
2684 int lineLength = content.getLine(caretLine).length();
2685 lineEndOffset = lineOffset + lineLength;
2686 }
2687 setCaretOffset(lineEndOffset, PREVIOUS_OFFSET_TRAILING);
2688 showCaret();
2689 }
2690 /**
2691 * Moves the caret to the beginning of the line.
2692 */
doLineStart()2693 void doLineStart() {
2694 int caretLine = getCaretLine();
2695 int lineOffset = content.getOffsetAtLine(caretLine);
2696 if (isWordWrap()) {
2697 TextLayout layout = renderer.getTextLayout(caretLine);
2698 int offsetInLine = caretOffset - lineOffset;
2699 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2700 int[] offsets = layout.getLineOffsets();
2701 lineOffset += offsets[lineIndex];
2702 renderer.disposeTextLayout(layout);
2703 }
2704 setCaretOffset(lineOffset, OFFSET_LEADING);
2705 showCaret();
2706 }
2707 /**
2708 * Moves the caret one line up and to the same character offset relative
2709 * to the beginning of the line. Move the caret to the end of the new line
2710 * if the new line is shorter than the character offset. Moves the caret to
2711 * the beginning of the document if it is already on the first line.
2712 */
doLineUp(boolean select)2713 void doLineUp(boolean select) {
2714 int caretLine = getCaretLine(), y = 0;
2715 boolean firstLine = false;
2716 if (isWordWrap()) {
2717 int lineOffset = content.getOffsetAtLine(caretLine);
2718 int offsetInLine = caretOffset - lineOffset;
2719 TextLayout layout = renderer.getTextLayout(caretLine);
2720 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2721 if (lineIndex == 0) {
2722 firstLine = caretLine == 0;
2723 if (!firstLine) {
2724 caretLine--;
2725 y = renderer.getLineHeight(caretLine) - 1;
2726 y--; // bug 485722: workaround for fractional line heights
2727 }
2728 } else {
2729 y = layout.getLineBounds(lineIndex - 1).y;
2730 y++; // bug 485722: workaround for fractional line heights
2731 }
2732 renderer.disposeTextLayout(layout);
2733 } else {
2734 firstLine = caretLine == 0;
2735 caretLine--;
2736 }
2737 if (firstLine) {
2738 setCaretOffset(0, SWT.DEFAULT);
2739 } else {
2740 int[] alignment = new int[1];
2741 int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
2742 setCaretOffset(offset, alignment[0]);
2743 }
2744 int oldColumnX = columnX;
2745 int oldHScrollOffset = horizontalScrollOffset;
2746 if (select) setMouseWordSelectionAnchor();
2747 showCaret();
2748 if (select) doSelection(ST.COLUMN_PREVIOUS);
2749 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2750 columnX = oldColumnX + hScrollChange;
2751 }
doMouseLinkCursor()2752 void doMouseLinkCursor() {
2753 Display display = getDisplay();
2754 Point point = display.getCursorLocation();
2755 point = display.map(null, this, point);
2756 doMouseLinkCursor(point.x, point.y);
2757 }
doMouseLinkCursor(int x, int y)2758 void doMouseLinkCursor(int x, int y) {
2759 int offset = getOffsetAtPoint(x, y, null, true);
2760 Display display = getDisplay();
2761 Cursor newCursor = cursor;
2762 if (renderer.hasLink(offset)) {
2763 newCursor = display.getSystemCursor(SWT.CURSOR_HAND);
2764 } else {
2765 if (cursor == null) {
2766 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
2767 newCursor = display.getSystemCursor(type);
2768 }
2769 }
2770 if (newCursor != getCursor()) super.setCursor(newCursor);
2771 }
2772 /**
2773 * Moves the caret to the specified location.
2774 *
2775 * @param x x location of the new caret position
2776 * @param y y location of the new caret position
2777 * @param select the location change is a selection operation.
2778 * include the line delimiter in the selection
2779 */
doMouseLocationChange(int x, int y, boolean select)2780 void doMouseLocationChange(int x, int y, boolean select) {
2781 int line = getLineIndex(y);
2782
2783 updateCaretDirection = true;
2784
2785 if (blockSelection) {
2786 x = Math.max(leftMargin, Math.min(x, clientAreaWidth - rightMargin));
2787 y = Math.max(topMargin, Math.min(y, clientAreaHeight - bottomMargin));
2788 if (doubleClickEnabled && clickCount > 1) {
2789 boolean wordSelect = (clickCount & 1) == 0;
2790 if (wordSelect) {
2791 Point left = getPointAtOffset(doubleClickSelection.x);
2792 int[] trailing = new int[1];
2793 int offset = getOffsetAtPoint(x, y, trailing, true);
2794 if (offset != -1) {
2795 if (x > left.x) {
2796 offset = getWordNext(offset + trailing[0], SWT.MOVEMENT_WORD_END);
2797 setBlockSelectionOffset(doubleClickSelection.x, offset, true);
2798 } else {
2799 offset = getWordPrevious(offset + trailing[0], SWT.MOVEMENT_WORD_START);
2800 setBlockSelectionOffset(doubleClickSelection.y, offset, true);
2801 }
2802 } else {
2803 if (x > left.x) {
2804 setBlockSelectionLocation(left.x, left.y, x, y, true);
2805 } else {
2806 Point right = getPointAtOffset(doubleClickSelection.y);
2807 setBlockSelectionLocation(right.x, right.y, x, y, true);
2808 }
2809 }
2810 } else {
2811 setBlockSelectionLocation(blockXLocation, y, true);
2812 }
2813 return;
2814 } else {
2815 if (select) {
2816 if (blockXLocation == -1) {
2817 setBlockSelectionOffset(caretOffset, false);
2818 }
2819 } else {
2820 clearBlockSelection(true, false);
2821 }
2822 int[] trailing = new int[1];
2823 int offset = getOffsetAtPoint(x, y, trailing, true);
2824 if (offset != -1) {
2825 if (select) {
2826 setBlockSelectionOffset(offset + trailing[0], true);
2827 return;
2828 }
2829 } else {
2830 if (isFixedLineHeight() && renderer.fixedPitch) {
2831 int avg = renderer.averageCharWidth;
2832 x = ((x + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
2833 }
2834 setBlockSelectionLocation(x, y, true);
2835 return;
2836 }
2837 }
2838 }
2839
2840 // allow caret to be placed below first line only if receiver is
2841 // not in single line mode. fixes 4820.
2842 if (line < 0 || (isSingleLine() && line > 0)) {
2843 return;
2844 }
2845 int[] alignment = new int[1];
2846 int newCaretOffset = getOffsetAtPoint(x, y, alignment);
2847 int newCaretAlignemnt = alignment[0];
2848
2849 if (doubleClickEnabled && clickCount > 1) {
2850 newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
2851 }
2852
2853 int newCaretLine = content.getLineAtOffset(newCaretOffset);
2854
2855 // Is the mouse within the left client area border or on
2856 // a different line? If not the autoscroll selection
2857 // could be incorrectly reset. Fixes 1GKM3XS
2858 boolean vchange = 0 <= y && y < clientAreaHeight || newCaretLine == 0 || newCaretLine == content.getLineCount() - 1;
2859 boolean hchange = 0 <= x && x < clientAreaWidth || wordWrap || newCaretLine != content.getLineAtOffset(caretOffset);
2860 if (vchange && hchange && (newCaretOffset != caretOffset || newCaretAlignemnt != caretAlignment)) {
2861 setCaretOffset(newCaretOffset, newCaretAlignemnt);
2862 if (select) doMouseSelection();
2863 showCaret();
2864 }
2865 if (!select) {
2866 setCaretOffset(newCaretOffset, newCaretAlignemnt);
2867 clearSelection(true);
2868 }
2869 }
2870 /**
2871 * Updates the selection based on the caret position
2872 */
2873 void doMouseSelection() {
2874 if (caretOffset <= selection.x ||
2875 (caretOffset > selection.x &&
2876 caretOffset < selection.y && selectionAnchor == selection.x)) {
2877 doSelection(ST.COLUMN_PREVIOUS);
2878 } else {
2879 doSelection(ST.COLUMN_NEXT);
2880 }
2881 }
2882 /**
2883 * Returns the offset of the word at the specified offset.
2884 * If the current selection extends from high index to low index
2885 * (i.e., right to left, or caret is at left border of selection on
2886 * non-bidi platforms) the start offset of the word preceding the
2887 * selection is returned. If the current selection extends from
2888 * low index to high index the end offset of the word following
2889 * the selection is returned.
2890 *
2891 * @param x mouse x location
2892 * @param newCaretOffset caret offset of the mouse cursor location
2893 * @param line line index of the mouse cursor location
2894 */
2895 int doMouseWordSelect(int x, int newCaretOffset, int line) {
2896 // flip selection anchor based on word selection direction from
2897 // base double click. Always do this here (and don't rely on doAutoScroll)
2898 // because auto scroll only does not cover all possible mouse selections
2899 // (e.g., mouse x < 0 && mouse y > caret line y)
2900 if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) {
2901 selectionAnchor = doubleClickSelection.y;
2902 } else if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) {
2903 selectionAnchor = doubleClickSelection.x;
2904 }
2905 if (0 <= x && x < clientAreaWidth) {
2906 boolean wordSelect = (clickCount & 1) == 0;
2907 if (caretOffset == selection.x) {
2908 if (wordSelect) {
2909 newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START);
2910 } else {
2911 newCaretOffset = content.getOffsetAtLine(line);
2912 }
2913 } else {
2914 if (wordSelect) {
2915 newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END);
2916 } else {
2917 int lineEnd = content.getCharCount();
2918 if (line + 1 < content.getLineCount()) {
2919 lineEnd = content.getOffsetAtLine(line + 1);
2920 }
2921 newCaretOffset = lineEnd;
2922 }
2923 }
2924 }
2925 return newCaretOffset;
2926 }
2927 /**
2928 * Scrolls one page down so that the last line (truncated or whole)
2929 * of the current page becomes the fully visible top line.
2930 * <p>
2931 * The caret is scrolled the same number of lines so that its location
2932 * relative to the top line remains the same. The exception is the end
2933 * of the text where a full page scroll is not possible. In this case
2934 * the caret is moved after the last character.
2935 * </p>
2936 *
2937 * @param select whether or not to select the page
2938 */
2939 void doPageDown(boolean select, int height) {
2940 if (isSingleLine()) return;
2941 int oldColumnX = columnX;
2942 int oldHScrollOffset = horizontalScrollOffset;
2943 if (isFixedLineHeight()) {
2944 int lineCount = content.getLineCount();
2945 int caretLine = getCaretLine();
2946 if (caretLine < lineCount - 1) {
2947 int lineHeight = renderer.getLineHeight();
2948 int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
2949 int scrollLines = Math.min(lineCount - caretLine - 1, lines);
2950 // ensure that scrollLines never gets negative and at least one
2951 // line is scrolled. fixes bug 5602.
2952 scrollLines = Math.max(1, scrollLines);
2953 int[] alignment = new int[1];
2954 int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines), alignment);
2955 setCaretOffset(offset, alignment[0]);
2956 if (select) {
2957 doSelection(ST.COLUMN_NEXT);
2958 }
2959 // scroll one page down or to the bottom
2960 int verticalMaximum = lineCount * getVerticalIncrement();
2961 int pageSize = clientAreaHeight;
2962 int verticalScrollOffset = getVerticalScrollOffset();
2963 int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
2964 if (scrollOffset + pageSize > verticalMaximum) {
2965 scrollOffset = verticalMaximum - pageSize;
2966 }
2967 if (scrollOffset > verticalScrollOffset) {
2968 scrollVertical(scrollOffset - verticalScrollOffset, true);
2969 }
2970 }
2971 } else {
2972 int lineCount = content.getLineCount();
2973 int caretLine = getCaretLine();
2974 int lineIndex, lineHeight;
2975 if (height == -1) {
2976 lineIndex = getPartialBottomIndex();
2977 int topY = getLinePixel(lineIndex);
2978 lineHeight = renderer.getLineHeight(lineIndex);
2979 height = topY;
2980 if (topY + lineHeight <= clientAreaHeight) {
2981 height += lineHeight;
2982 } else {
2983 if (isWordWrap()) {
2984 TextLayout layout = renderer.getTextLayout(lineIndex);
2985 int y = clientAreaHeight - topY;
2986 for (int i = 0; i < layout.getLineCount(); i++) {
2987 Rectangle bounds = layout.getLineBounds(i);
2988 if (bounds.contains(bounds.x, y)) {
2989 height += bounds.y;
2990 break;
2991 }
2992 }
2993 renderer.disposeTextLayout(layout);
2994 }
2995 }
2996 } else {
2997 lineIndex = getLineIndex(height);
2998 int topLineY = getLinePixel(lineIndex);
2999 if (isWordWrap()) {
3000 TextLayout layout = renderer.getTextLayout(lineIndex);
3001 int y = height - topLineY;
3002 for (int i = 0; i < layout.getLineCount(); i++) {
3003 Rectangle bounds = layout.getLineBounds(i);
3004 if (bounds.contains(bounds.x, y)) {
3005 height = topLineY + bounds.y + bounds.height;
3006 break;
3007 }
3008 }
3009 renderer.disposeTextLayout(layout);
3010 } else {
3011 height = topLineY + renderer.getLineHeight(lineIndex);
3012 }
3013 }
3014 int caretHeight = height;
3015 if (isWordWrap()) {
3016 TextLayout layout = renderer.getTextLayout(caretLine);
3017 int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
3018 lineIndex = getVisualLineIndex(layout, offsetInLine);
3019 caretHeight += layout.getLineBounds(lineIndex).y;
3020 renderer.disposeTextLayout(layout);
3021 }
3022 lineIndex = caretLine;
3023 lineHeight = renderer.getLineHeight(lineIndex);
3024 while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
3025 caretHeight -= lineHeight;
3026 lineHeight = renderer.getLineHeight(++lineIndex);
3027 }
3028 int[] alignment = new int[1];
3029 int offset = getOffsetAtPoint(columnX, caretHeight, lineIndex, alignment);
3030 setCaretOffset(offset, alignment[0]);
3031 if (select) doSelection(ST.COLUMN_NEXT);
3032 height = getAvailableHeightBellow(height);
3033 scrollVertical(height, true);
3034 if (height == 0) setCaretLocation();
3035 }
3036 showCaret();
3037 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
3038 columnX = oldColumnX + hScrollChange;
3039 }
3040 /**
3041 * Moves the cursor to the end of the last fully visible line.
3042 */
3043 void doPageEnd() {
3044 // go to end of line if in single line mode. fixes 5673
3045 if (isSingleLine()) {
3046 doLineEnd();
3047 } else {
3048 int bottomOffset;
3049 if (isWordWrap()) {
3050 int lineIndex = getPartialBottomIndex();
3051 TextLayout layout = renderer.getTextLayout(lineIndex);
3052 int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);
3053 int index = layout.getLineCount() - 1;
3054 while (index >= 0) {
3055 Rectangle bounds = layout.getLineBounds(index);
3056 if (y >= bounds.y + bounds.height) break;
3057 index--;
3058 }
3059 if (index == -1 && lineIndex > 0) {
3060 bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length();
3061 } else {
3062 bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1);
3063 }
3064 renderer.disposeTextLayout(layout);
3065 } else {
3066 int lineIndex = getBottomIndex();
3067 bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length();
3068 }
3069 if (caretOffset < bottomOffset) {
3070 setCaretOffset(bottomOffset, OFFSET_LEADING);
3071 showCaret();
3072 }
3073 }
3074 }
3075 /**
3076 * Moves the cursor to the beginning of the first fully visible line.
3077 */
3078 void doPageStart() {
3079 int topOffset;
3080 if (isWordWrap()) {
3081 int y, lineIndex;
3082 if (topIndexY > 0) {
3083 lineIndex = topIndex - 1;
3084 y = renderer.getLineHeight(lineIndex) - topIndexY;
3085 } else {
3086 lineIndex = topIndex;
3087 y = -topIndexY;
3088 }
3089 TextLayout layout = renderer.getTextLayout(lineIndex);
3090 int index = 0;
3091 int lineCount = layout.getLineCount();
3092 while (index < lineCount) {
3093 Rectangle bounds = layout.getLineBounds(index);
3094 if (y <= bounds.y) break;
3095 index++;
3096 }
3097 if (index == lineCount) {
3098 topOffset = content.getOffsetAtLine(lineIndex + 1);
3099 } else {
3100 topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
3101 }
3102 renderer.disposeTextLayout(layout);
3103 } else {
3104 topOffset = content.getOffsetAtLine(topIndex);
3105 }
3106 if (caretOffset > topOffset) {
3107 setCaretOffset(topOffset, OFFSET_LEADING);
3108 showCaret();
3109 }
3110 }
3111 /**
3112 * Scrolls one page up so that the first line (truncated or whole)
3113 * of the current page becomes the fully visible last line.
3114 * The caret is scrolled the same number of lines so that its location
3115 * relative to the top line remains the same. The exception is the beginning
3116 * of the text where a full page scroll is not possible. In this case the
3117 * caret is moved in front of the first character.
3118 */
3119 void doPageUp(boolean select, int height) {
3120 if (isSingleLine()) return;
3121 int oldHScrollOffset = horizontalScrollOffset;
3122 int oldColumnX = columnX;
3123 if (isFixedLineHeight()) {
3124 int caretLine = getCaretLine();
3125 if (caretLine > 0) {
3126 int lineHeight = renderer.getLineHeight();
3127 int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
3128 int scrollLines = Math.max(1, Math.min(caretLine, lines));
3129 caretLine -= scrollLines;
3130 int[] alignment = new int[1];
3131 int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine), alignment);
3132 setCaretOffset(offset, alignment[0]);
3133 if (select) {
3134 doSelection(ST.COLUMN_PREVIOUS);
3135 }
3136 int verticalScrollOffset = getVerticalScrollOffset();
3137 int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
3138 if (scrollOffset < verticalScrollOffset) {
3139 scrollVertical(scrollOffset - verticalScrollOffset, true);
3140 }
3141 }
3142 } else {
3143 int caretLine = getCaretLine();
3144 int lineHeight, lineIndex;
3145 if (height == -1) {
3146 if (topIndexY == 0) {
3147 height = clientAreaHeight;
3148 } else {
3149 int y;
3150 if (topIndex > 0) {
3151 lineIndex = topIndex - 1;
3152 lineHeight = renderer.getLineHeight(lineIndex);
3153 height = clientAreaHeight - topIndexY;
3154 y = lineHeight - topIndexY;
3155 } else {
3156 lineIndex = topIndex;
3157 lineHeight = renderer.getLineHeight(lineIndex);
3158 height = clientAreaHeight - (lineHeight + topIndexY);
3159 y = -topIndexY;
3160 }
3161 if (isWordWrap()) {
3162 TextLayout layout = renderer.getTextLayout(lineIndex);
3163 for (int i = 0; i < layout.getLineCount(); i++) {
3164 Rectangle bounds = layout.getLineBounds(i);
3165 if (bounds.contains(bounds.x, y)) {
3166 height += lineHeight - (bounds.y + bounds.height);
3167 break;
3168 }
3169 }
3170 renderer.disposeTextLayout(layout);
3171 }
3172 }
3173 } else {
3174 lineIndex = getLineIndex(clientAreaHeight - height);
3175 int topLineY = getLinePixel(lineIndex);
3176 if (isWordWrap()) {
3177 TextLayout layout = renderer.getTextLayout(lineIndex);
3178 int y = topLineY;
3179 for (int i = 0; i < layout.getLineCount(); i++) {
3180 Rectangle bounds = layout.getLineBounds(i);
3181 if (bounds.contains(bounds.x, y)) {
3182 height = clientAreaHeight - (topLineY + bounds.y);
3183 break;
3184 }
3185 }
3186 renderer.disposeTextLayout(layout);
3187 } else {
3188 height = clientAreaHeight - topLineY;
3189 }
3190 }
3191 int caretHeight = height;
3192 if (isWordWrap()) {
3193 TextLayout layout = renderer.getTextLayout(caretLine);
3194 int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
3195 lineIndex = getVisualLineIndex(layout, offsetInLine);
3196 caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
3197 renderer.disposeTextLayout(layout);
3198 }
3199 lineIndex = caretLine;
3200 lineHeight = renderer.getLineHeight(lineIndex);
3201 while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
3202 caretHeight -= lineHeight;
3203 lineHeight = renderer.getLineHeight(--lineIndex);
3204 }
3205 lineHeight = renderer.getLineHeight(lineIndex);
3206 int[] alignment = new int[1];
3207 int offset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex, alignment);
3208 setCaretOffset(offset, alignment[0]);
3209 if (select) doSelection(ST.COLUMN_PREVIOUS);
3210 height = getAvailableHeightAbove(height);
3211 scrollVertical(-height, true);
3212 if (height == 0) setCaretLocation();
3213 }
3214 showCaret();
3215 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
3216 columnX = oldColumnX + hScrollChange;
3217 }
3218 /**
3219 * Updates the selection to extend to the current caret position.
3220 */
3221 void doSelection(int direction) {
3222 int redrawStart = -1;
3223 int redrawEnd = -1;
3224 if (selectionAnchor == -1) {
3225 selectionAnchor = selection.x;
3226 }
3227 if (direction == ST.COLUMN_PREVIOUS) {
3228 if (caretOffset < selection.x) {
3229 // grow selection
3230 redrawEnd = selection.x;
3231 redrawStart = selection.x = caretOffset;
3232 // check if selection has reversed direction
3233 if (selection.y != selectionAnchor) {
3234 redrawEnd = selection.y;
3235 selection.y = selectionAnchor;
3236 }
3237 // test whether selection actually changed. Fixes 1G71EO1
3238 } else if (selectionAnchor == selection.x && caretOffset < selection.y) {
3239 // caret moved towards selection anchor (left side of selection).
3240 // shrink selection
3241 redrawEnd = selection.y;
3242 redrawStart = selection.y = caretOffset;
3243 }
3244 } else {
3245 if (caretOffset > selection.y) {
3246 // grow selection
3247 redrawStart = selection.y;
3248 redrawEnd = selection.y = caretOffset;
3249 // check if selection has reversed direction
3250 if (selection.x != selectionAnchor) {
3251 redrawStart = selection.x;
3252 selection.x = selectionAnchor;
3253 }
3254 // test whether selection actually changed. Fixes 1G71EO1
3255 } else if (selectionAnchor == selection.y && caretOffset > selection.x) {
3256 // caret moved towards selection anchor (right side of selection).
3257 // shrink selection
3258 redrawStart = selection.x;
3259 redrawEnd = selection.x = caretOffset;
3260 }
3261 }
3262 if (redrawStart != -1 && redrawEnd != -1) {
3263 internalRedrawRange(redrawStart, redrawEnd - redrawStart);
3264 sendSelectionEvent();
3265 }
3266 sendAccessibleTextCaretMoved();
3267 }
3268 /**
3269 * Moves the caret to the next character or to the beginning of the
3270 * next line if the cursor is at the end of a line.
3271 */
3272 void doSelectionCursorNext() {
3273 int caretLine = getCaretLine();
3274 int lineOffset = content.getOffsetAtLine(caretLine);
3275 int offsetInLine = caretOffset - lineOffset;
3276 int offset, alignment;
3277 if (offsetInLine < content.getLine(caretLine).length()) {
3278 TextLayout layout = renderer.getTextLayout(caretLine);
3279 offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
3280 int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
3281 renderer.disposeTextLayout(layout);
3282 offset = offsetInLine + lineOffset;
3283 alignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
3284 setCaretOffset(offset, alignment);
3285 showCaret();
3286 } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
3287 caretLine++;
3288 offset = content.getOffsetAtLine(caretLine);
3289 alignment = PREVIOUS_OFFSET_TRAILING;
3290 setCaretOffset(offset, alignment);
3291 showCaret();
3292 }
3293 }
3294 /**
3295 * Moves the caret to the previous character or to the end of the previous
3296 * line if the cursor is at the beginning of a line.
3297 */
3298 void doSelectionCursorPrevious() {
3299 int caretLine = getCaretLine();
3300 int lineOffset = content.getOffsetAtLine(caretLine);
3301 int offsetInLine = caretOffset - lineOffset;
3302 if (offsetInLine > 0) {
3303 int offset = getClusterPrevious(caretOffset, caretLine);
3304 setCaretOffset(offset, OFFSET_LEADING);
3305 showCaret();
3306 } else if (caretLine > 0) {
3307 caretLine--;
3308 lineOffset = content.getOffsetAtLine(caretLine);
3309 int offset = lineOffset + content.getLine(caretLine).length();
3310 setCaretOffset(offset, OFFSET_LEADING);
3311 showCaret();
3312 }
3313 }
3314 /**
3315 * Moves the caret one line down and to the same character offset relative
3316 * to the beginning of the line. Moves the caret to the end of the new line
3317 * if the new line is shorter than the character offset.
3318 * Moves the caret to the end of the text if the caret already is on the
3319 * last line.
3320 * Adjusts the selection according to the caret change. This can either add
3321 * to or subtract from the old selection, depending on the previous selection
3322 * direction.
3323 */
3324 void doSelectionLineDown() {
3325 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3326 doLineDown(true);
3327 columnX = oldColumnX;
3328 }
3329 /**
3330 * Moves the caret one line up and to the same character offset relative
3331 * to the beginning of the line. Moves the caret to the end of the new line
3332 * if the new line is shorter than the character offset.
3333 * Moves the caret to the beginning of the document if it is already on the
3334 * first line.
3335 * Adjusts the selection according to the caret change. This can either add
3336 * to or subtract from the old selection, depending on the previous selection
3337 * direction.
3338 */
3339 void doSelectionLineUp() {
3340 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3341 doLineUp(true);
3342 columnX = oldColumnX;
3343 }
3344 /**
3345 * Scrolls one page down so that the last line (truncated or whole)
3346 * of the current page becomes the fully visible top line.
3347 * <p>
3348 * The caret is scrolled the same number of lines so that its location
3349 * relative to the top line remains the same. The exception is the end
3350 * of the text where a full page scroll is not possible. In this case
3351 * the caret is moved after the last character.
3352 * </p><p>
3353 * Adjusts the selection according to the caret change. This can either add
3354 * to or subtract from the old selection, depending on the previous selection
3355 * direction.
3356 * </p>
3357 */
3358 void doSelectionPageDown(int pixels) {
3359 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3360 doPageDown(true, pixels);
3361 columnX = oldColumnX;
3362 }
3363 /**
3364 * Scrolls one page up so that the first line (truncated or whole)
3365 * of the current page becomes the fully visible last line.
3366 * <p>
3367 * The caret is scrolled the same number of lines so that its location
3368 * relative to the top line remains the same. The exception is the beginning
3369 * of the text where a full page scroll is not possible. In this case the
3370 * caret is moved in front of the first character.
3371 * </p><p>
3372 * Adjusts the selection according to the caret change. This can either add
3373 * to or subtract from the old selection, depending on the previous selection
3374 * direction.
3375 * </p>
3376 */
3377 void doSelectionPageUp(int pixels) {
3378 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3379 doPageUp(true, pixels);
3380 columnX = oldColumnX;
3381 }
3382 /**
3383 * Moves the caret to the end of the next word .
3384 */
3385 void doSelectionWordNext() {
3386 int offset = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
3387 // don't change caret position if in single line mode and the cursor
3388 // would be on a different line. fixes 5673
3389 if (!isSingleLine() ||
3390 content.getLineAtOffset(caretOffset) == content.getLineAtOffset(offset)) {
3391 // Force symmetrical movement for word next and previous. Fixes 14536
3392 setCaretOffset(offset, OFFSET_LEADING);
3393 showCaret();
3394 }
3395 }
3396 /**
3397 * Moves the caret to the start of the previous word.
3398 */
3399 void doSelectionWordPrevious() {
3400 int offset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
3401 setCaretOffset(offset, OFFSET_LEADING);
3402 showCaret();
3403 }
3404 /**
3405 * Moves the caret one character to the left. Do not go to the previous line.
3406 * When in a bidi locale and at a R2L character the caret is moved to the
3407 * beginning of the R2L segment (visually right) and then one character to the
3408 * left (visually left because it's now in a L2R segment).
3409 */
3410 void doVisualPrevious() {
3411 int offset = getClusterPrevious(caretOffset, getCaretLine());
3412 setCaretOffset(offset, SWT.DEFAULT);
3413 showCaret();
3414 }
3415 /**
3416 * Moves the caret one character to the right. Do not go to the next line.
3417 * When in a bidi locale and at a R2L character the caret is moved to the
3418 * end of the R2L segment (visually left) and then one character to the
3419 * right (visually right because it's now in a L2R segment).
3420 */
3421 void doVisualNext() {
3422 int offset = getClusterNext(caretOffset, getCaretLine());
3423 setCaretOffset(offset, SWT.DEFAULT);
3424 showCaret();
3425 }
3426 /**
3427 * Moves the caret to the end of the next word.
3428 * If a selection exists, move the caret to the end of the selection
3429 * and remove the selection.
3430 */
3431 void doWordNext() {
3432 if (selection.y - selection.x > 0) {
3433 setCaretOffset(selection.y, SWT.DEFAULT);
3434 showCaret();
3435 } else {
3436 doSelectionWordNext();
3437 }
3438 }
3439 /**
3440 * Moves the caret to the start of the previous word.
3441 * If a selection exists, move the caret to the start of the selection
3442 * and remove the selection.
3443 */
3444 void doWordPrevious() {
3445 if (selection.y - selection.x > 0) {
3446 setCaretOffset(selection.x, SWT.DEFAULT);
3447 showCaret();
3448 } else {
3449 doSelectionWordPrevious();
3450 }
3451 }
3452 /**
3453 * Ends the autoscroll process.
3454 */
3455 void endAutoScroll() {
3456 autoScrollDirection = SWT.NULL;
3457 }
3458 @Override
3459 public Color getBackground() {
3460 checkWidget();
3461 if (background == null) {
3462 return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
3463 }
3464 return background;
3465 }
3466 /**
3467 * Returns the baseline, in points.
3468 *
3469 * Note: this API should not be used if a StyleRange attribute causes lines to
3470 * have different heights (i.e. different fonts, rise, etc).
3471 *
3472 * @return baseline the baseline
3473 * @exception SWTException <ul>
3474 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3475 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3476 * </ul>
3477 * @since 3.0
3478 *
3479 * @see #getBaseline(int)
3480 */
3481 public int getBaseline() {
3482 checkWidget();
3483 return renderer.getBaseline();
3484 }
3485 /**
3486 * Returns the baseline at the given offset, in points.
3487 *
3488 * @param offset the offset
3489 *
3490 * @return baseline the baseline
3491 *
3492 * @exception SWTException <ul>
3493 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3494 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3495 * </ul>
3496 * @exception IllegalArgumentException <ul>
3497 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3498 * </ul>
3499 *
3500 * @since 3.2
3501 */
3502 public int getBaseline(int offset) {
3503 checkWidget();
3504 if (!(0 <= offset && offset <= content.getCharCount())) {
3505 SWT.error(SWT.ERROR_INVALID_RANGE);
3506 }
3507 if (isFixedLineHeight()) {
3508 return renderer.getBaseline();
3509 }
3510 int lineIndex = content.getLineAtOffset(offset);
3511 int lineOffset = content.getOffsetAtLine(lineIndex);
3512 TextLayout layout = renderer.getTextLayout(lineIndex);
3513 int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
3514 FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
3515 renderer.disposeTextLayout(layout);
3516 return metrics.getAscent() + metrics.getLeading();
3517 }
3518 /**
3519 * Gets the BIDI coloring mode. When true the BIDI text display
3520 * algorithm is applied to segments of text that are the same
3521 * color.
3522 *
3523 * @return the current coloring mode
3524 * @exception SWTException <ul>
3525 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3526 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3527 * </ul>
3528 *
3529 * @deprecated use BidiSegmentListener instead.
3530 */
3531 @Deprecated
3532 public boolean getBidiColoring() {
3533 checkWidget();
3534 return bidiColoring;
3535 }
3536 /**
3537 * Returns whether the widget is in block selection mode.
3538 *
3539 * @return true if widget is in block selection mode, false otherwise
3540 *
3541 * @exception SWTException <ul>
3542 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3543 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3544 * </ul>
3545 *
3546 * @since 3.5
3547 */
3548 public boolean getBlockSelection() {
3549 checkWidget();
3550 return blockSelection;
3551 }
3552 Rectangle getBlockSelectionPosition() {
3553 int firstLine = getLineIndex(blockYAnchor - getVerticalScrollOffset());
3554 int lastLine = getLineIndex(blockYLocation - getVerticalScrollOffset());
3555 if (firstLine > lastLine) {
3556 int temp = firstLine;
3557 firstLine = lastLine;
3558 lastLine = temp;
3559 }
3560 int left = blockXAnchor;
3561 int right = blockXLocation;
3562 if (left > right) {
3563 left = blockXLocation;
3564 right = blockXAnchor;
3565 }
3566 return new Rectangle (left - horizontalScrollOffset, firstLine, right - horizontalScrollOffset, lastLine);
3567 }
3568 /**
3569 * Returns the block selection bounds. The bounds is
3570 * relative to the upper left corner of the document.
3571 *
3572 * @return the block selection bounds
3573 *
3574 * @exception SWTException <ul>
3575 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3576 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3577 * </ul>
3578 *
3579 * @since 3.5
3580 */
3581 public Rectangle getBlockSelectionBounds() {
3582 Rectangle rect;
3583 if (blockSelection && blockXLocation != -1) {
3584 rect = getBlockSelectionRectangle();
3585 } else {
3586 Point startPoint = getPointAtOffset(selection.x);
3587 Point endPoint = getPointAtOffset(selection.y);
3588 int height = getLineHeight(selection.y);
3589 rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y);
3590 if (selection.x == selection.y) {
3591 rect.width = getCaretWidth();
3592 }
3593 }
3594 rect.x += horizontalScrollOffset;
3595 rect.y += getVerticalScrollOffset();
3596 return rect;
3597 }
3598 Rectangle getBlockSelectionRectangle() {
3599 Rectangle rect = getBlockSelectionPosition();
3600 rect.y = getLinePixel(rect.y);
3601 rect.width = rect.width - rect.x;
3602 rect.height = getLinePixel(rect.height + 1) - rect.y;
3603 return rect;
3604 }
3605 String getBlockSelectionText(String delimiter) {
3606 Rectangle rect = getBlockSelectionPosition();
3607 int firstLine = rect.y;
3608 int lastLine = rect.height;
3609 int left = rect.x;
3610 int right = rect.width;
3611 StringBuilder buffer = new StringBuilder();
3612 for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
3613 int start = getOffsetAtPoint(left, 0, lineIndex, null);
3614 int end = getOffsetAtPoint(right, 0, lineIndex, null);
3615 if (start > end) {
3616 int temp = start;
3617 start = end;
3618 end = temp;
3619 }
3620 String text = content.getTextRange(start, end - start);
3621 buffer.append(text);
3622 if (lineIndex < lastLine) buffer.append(delimiter);
3623 }
3624 return buffer.toString();
3625 }
3626 /**
3627 * Returns the index of the last fully visible line.
3628 *
3629 * @return index of the last fully visible line.
3630 */
3631 int getBottomIndex() {
3632 int bottomIndex;
3633 if (isFixedLineHeight()) {
3634 int lineCount = 1;
3635 int lineHeight = renderer.getLineHeight();
3636 if (lineHeight != 0) {
3637 // calculate the number of lines that are fully visible
3638 int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
3639 lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
3640 }
3641 bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
3642 } else {
3643 int clientAreaHeight = this.clientAreaHeight - bottomMargin;
3644 bottomIndex = getLineIndex(clientAreaHeight);
3645 if (bottomIndex > 0) {
3646 int linePixel = getLinePixel(bottomIndex);
3647 int lineHeight = renderer.getLineHeight(bottomIndex);
3648 if (linePixel + lineHeight > clientAreaHeight) {
3649 if (getLinePixel(bottomIndex - 1) >= topMargin) {
3650 bottomIndex--;
3651 }
3652 }
3653 }
3654 }
3655 return bottomIndex;
3656 }
3657 /**
3658 * Returns the bottom margin.
3659 *
3660 * @return the bottom margin.
3661 * @exception SWTException <ul>
3662 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3663 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3664 * </ul>
3665 *
3666 * @since 3.5
3667 */
3668 public int getBottomMargin() {
3669 checkWidget();
3670 return bottomMargin;
3671 }
3672 Rectangle getBoundsAtOffset(int offset) {
3673 int lineIndex = content.getLineAtOffset(offset);
3674 int lineOffset = content.getOffsetAtLine(lineIndex);
3675 String line = content.getLine(lineIndex);
3676 Rectangle bounds;
3677 if (line.length() != 0) {
3678 TextLayout layout = renderer.getTextLayout(lineIndex);
3679 int offsetInLine = Math.min (layout.getText().length(), Math.max (0, offset - lineOffset));
3680 bounds = layout.getBounds(offsetInLine, offsetInLine);
3681 if (getListeners(ST.LineGetSegments).length > 0 && caretAlignment == PREVIOUS_OFFSET_TRAILING && offsetInLine != 0) {
3682 offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
3683 Point point = layout.getLocation(offsetInLine, true);
3684 bounds = new Rectangle (point.x, point.y, 0, bounds.height);
3685 }
3686 renderer.disposeTextLayout(layout);
3687 } else {
3688 bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
3689 }
3690 if (offset == caretOffset && !isWordWrap()) {
3691 int lineEnd = lineOffset + line.length();
3692 if (offset == lineEnd) {
3693 bounds.width += getCaretWidth();
3694 }
3695 }
3696 bounds.x += leftMargin - horizontalScrollOffset;
3697 bounds.y += getLinePixel(lineIndex);
3698 return bounds;
3699 }
3700 /**
3701 * Returns the caret position relative to the start of the text.
3702 *
3703 * @return the caret position relative to the start of the text.
3704 * @exception SWTException <ul>
3705 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3706 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3707 * </ul>
3708 */
3709 public int getCaretOffset() {
3710 checkWidget();
3711 return caretOffset;
3712 }
3713 /**
3714 * Returns the caret width.
3715 *
3716 * @return the caret width, 0 if caret is null.
3717 */
3718 int getCaretWidth() {
3719 Caret caret = getCaret();
3720 if (caret == null) return 0;
3721 return caret.getSize().x;
3722 }
3723 Object getClipboardContent(int clipboardType) {
3724 TextTransfer plainTextTransfer = TextTransfer.getInstance();
3725 return clipboard.getContents(plainTextTransfer, clipboardType);
3726 }
3727 int getClusterNext(int offset, int lineIndex) {
3728 int lineOffset = content.getOffsetAtLine(lineIndex);
3729 TextLayout layout = renderer.getTextLayout(lineIndex);
3730 offset -= lineOffset;
3731 offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
3732 offset += lineOffset;
3733 renderer.disposeTextLayout(layout);
3734 return offset;
3735 }
3736 int getClusterPrevious(int offset, int lineIndex) {
3737 int lineOffset = content.getOffsetAtLine(lineIndex);
3738 TextLayout layout = renderer.getTextLayout(lineIndex);
3739 offset -= lineOffset;
3740 offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
3741 offset += lineOffset;
3742 renderer.disposeTextLayout(layout);
3743 return offset;
3744 }
3745 /**
3746 * Returns the content implementation that is used for text storage.
3747 *
3748 * @return content the user defined content implementation that is used for
3749 * text storage or the default content implementation if no user defined
3750 * content implementation has been set.
3751 * @exception SWTException <ul>
3752 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3753 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3754 * </ul>
3755 */
3756 public StyledTextContent getContent() {
3757 checkWidget();
3758 return content;
3759 }
3760 @Override
3761 public boolean getDragDetect () {
3762 checkWidget ();
3763 return dragDetect;
3764 }
3765 /**
3766 * Returns whether the widget implements double click mouse behavior.
3767 *
3768 * @return true if double clicking a word selects the word, false if double clicks
3769 * have the same effect as regular mouse clicks
3770 * @exception SWTException <ul>
3771 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3772 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3773 * </ul>
3774 */
3775 public boolean getDoubleClickEnabled() {
3776 checkWidget();
3777 return doubleClickEnabled;
3778 }
3779 /**
3780 * Returns whether the widget content can be edited.
3781 *
3782 * @return true if content can be edited, false otherwise
3783 * @exception SWTException <ul>
3784 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3785 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3786 * </ul>
3787 */
3788 public boolean getEditable() {
3789 checkWidget();
3790 return editable;
3791 }
3792 @Override
3793 public Color getForeground() {
3794 checkWidget();
3795 if (foreground == null) {
3796 return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
3797 }
3798 return foreground;
3799 }
3800 /**
3801 * Returns the horizontal scroll increment.
3802 *
3803 * @return horizontal scroll increment.
3804 */
3805 int getHorizontalIncrement() {
3806 return renderer.averageCharWidth;
3807 }
3808 /**
3809 * Returns the horizontal scroll offset relative to the start of the line.
3810 *
3811 * @return horizontal scroll offset relative to the start of the line,
3812 * measured in character increments starting at 0, if > 0 the content is scrolled
3813 * @exception SWTException <ul>
3814 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3815 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3816 * </ul>
3817 */
3818 public int getHorizontalIndex() {
3819 checkWidget();
3820 return horizontalScrollOffset / getHorizontalIncrement();
3821 }
3822 /**
3823 * Returns the horizontal scroll offset relative to the start of the line.
3824 *
3825 * @return the horizontal scroll offset relative to the start of the line,
3826 * measured in SWT logical point starting at 0, if > 0 the content is scrolled.
3827 * @exception SWTException <ul>
3828 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3829 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3830 * </ul>
3831 */
3832 public int getHorizontalPixel() {
3833 checkWidget();
3834 return horizontalScrollOffset;
3835 }
3836 /**
3837 * Returns the line indentation of the widget.
3838 *
3839 * @return the line indentation
3840 *
3841 * @exception SWTException <ul>
3842 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3843 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3844 * </ul>
3845 *
3846 * @see #getLineIndent(int)
3847 *
3848 * @since 3.2
3849 */
3850 public int getIndent() {
3851 checkWidget();
3852 return indent;
3853 }
3854 /**
3855 * Returns whether the widget justifies lines.
3856 *
3857 * @return whether lines are justified
3858 *
3859 * @exception SWTException <ul>
3860 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3861 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3862 * </ul>
3863 *
3864 * @see #getLineJustify(int)
3865 *
3866 * @since 3.2
3867 */
3868 public boolean getJustify() {
3869 checkWidget();
3870 return justify;
3871 }
3872 /**
3873 * Returns the action assigned to the key.
3874 * Returns SWT.NULL if there is no action associated with the key.
3875 *
3876 * @param key a key code defined in SWT.java or a character.
3877 * Optionally ORd with a state mask. Preferred state masks are one or more of
3878 * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
3879 * differences. However, there may be cases where using the specific state masks
3880 * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
3881 * @return one of the predefined actions defined in ST.java or SWT.NULL
3882 * if there is no action associated with the key.
3883 * @exception SWTException <ul>
3884 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3885 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3886 * </ul>
3887 */
3888 public int getKeyBinding(int key) {
3889 checkWidget();
3890 Integer action = keyActionMap.get(key);
3891 return action == null ? SWT.NULL : action.intValue();
3892 }
3893 /**
3894 * Gets the number of characters.
3895 *
3896 * @return number of characters in the widget
3897 * @exception SWTException <ul>
3898 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3899 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3900 * </ul>
3901 */
3902 public int getCharCount() {
3903 checkWidget();
3904 return content.getCharCount();
3905 }
3906 /**
3907 * Returns the line at the given line index without delimiters.
3908 * Index 0 is the first line of the content. When there are not
3909 * any lines, getLine(0) is a valid call that answers an empty string.
3910 * <p>
3911 *
3912 * @param lineIndex index of the line to return.
3913 * @return the line text without delimiters
3914 *
3915 * @exception SWTException <ul>
3916 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3917 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3918 * </ul>
3919 * @exception IllegalArgumentException <ul>
3920 * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
3921 * </ul>
3922 * @since 3.4
3923 */
3924 public String getLine(int lineIndex) {
3925 checkWidget();
3926 if (lineIndex < 0 ||
3927 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
3928 SWT.error(SWT.ERROR_INVALID_RANGE);
3929 }
3930 return content.getLine(lineIndex);
3931 }
3932 /**
3933 * Returns the alignment of the line at the given index.
3934 *
3935 * @param index the index of the line
3936 *
3937 * @return the line alignment
3938 *
3939 * @exception SWTException <ul>
3940 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3941 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3942 * </ul>
3943 * @exception IllegalArgumentException <ul>
3944 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3945 * </ul>
3946 *
3947 * @see #getAlignment()
3948 *
3949 * @since 3.2
3950 */
getLineAlignment(int index)3951 public int getLineAlignment(int index) {
3952 checkWidget();
3953 if (index < 0 || index > content.getLineCount()) {
3954 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3955 }
3956 return renderer.getLineAlignment(index, alignment);
3957 }
3958 /**
3959 * Returns the line at the specified offset in the text
3960 * where 0 < offset < getCharCount() so that getLineAtOffset(getCharCount())
3961 * returns the line of the insert location.
3962 *
3963 * @param offset offset relative to the start of the content.
3964 * 0 <= offset <= getCharCount()
3965 * @return line at the specified offset in the text
3966 * @exception SWTException <ul>
3967 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3968 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3969 * </ul>
3970 * @exception IllegalArgumentException <ul>
3971 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3972 * </ul>
3973 */
getLineAtOffset(int offset)3974 public int getLineAtOffset(int offset) {
3975 checkWidget();
3976 if (offset < 0 || offset > getCharCount()) {
3977 SWT.error(SWT.ERROR_INVALID_RANGE);
3978 }
3979 return content.getLineAtOffset(offset);
3980 }
3981 /**
3982 * Returns the background color of the line at the given index.
3983 * Returns null if a LineBackgroundListener has been set or if no background
3984 * color has been specified for the line. Should not be called if a
3985 * LineBackgroundListener has been set since the listener maintains the
3986 * line background colors.
3987 *
3988 * @param index the index of the line
3989 * @return the background color of the line at the given index.
3990 *
3991 * @exception SWTException <ul>
3992 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3993 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3994 * </ul>
3995 * @exception IllegalArgumentException <ul>
3996 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3997 * </ul>
3998 */
getLineBackground(int index)3999 public Color getLineBackground(int index) {
4000 checkWidget();
4001 if (index < 0 || index > content.getLineCount()) {
4002 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4003 }
4004 return isListening(ST.LineGetBackground) ? null : renderer.getLineBackground(index, null);
4005 }
4006 /**
4007 * Returns the bullet of the line at the given index.
4008 *
4009 * @param index the index of the line
4010 *
4011 * @return the line bullet
4012 *
4013 * @exception SWTException <ul>
4014 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4015 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4016 * </ul>
4017 * @exception IllegalArgumentException <ul>
4018 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4019 * </ul>
4020 *
4021 * @since 3.2
4022 */
getLineBullet(int index)4023 public Bullet getLineBullet(int index) {
4024 checkWidget();
4025 if (index < 0 || index > content.getLineCount()) {
4026 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4027 }
4028 return isListening(ST.LineGetStyle) ? null : renderer.getLineBullet(index, null);
4029 }
4030 /**
4031 * Returns the line background data for the given line or null if
4032 * there is none.
4033 *
4034 * @param lineOffset offset of the line start relative to the start
4035 * of the content.
4036 * @param line line to get line background data for
4037 * @return line background data for the given line.
4038 */
getLineBackgroundData(int lineOffset, String line)4039 StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
4040 return sendLineEvent(ST.LineGetBackground, lineOffset, line);
4041 }
4042 /**
4043 * Gets the number of text lines.
4044 *
4045 * @return the number of lines in the widget
4046 * @exception SWTException <ul>
4047 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4048 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4049 * </ul>
4050 */
getLineCount()4051 public int getLineCount() {
4052 checkWidget();
4053 return content.getLineCount();
4054 }
4055 /**
4056 * Returns the number of lines that can be completely displayed in the
4057 * widget client area.
4058 *
4059 * @return number of lines that can be completely displayed in the widget
4060 * client area.
4061 */
getLineCountWhole()4062 int getLineCountWhole() {
4063 if (isFixedLineHeight()) {
4064 int lineHeight = renderer.getLineHeight();
4065 return lineHeight != 0 ? clientAreaHeight / lineHeight : 1;
4066 }
4067 return getBottomIndex() - topIndex + 1;
4068 }
4069 /**
4070 * Returns the line delimiter used for entering new lines by key down
4071 * or paste operation.
4072 *
4073 * @return line delimiter used for entering new lines by key down
4074 * or paste operation.
4075 * @exception SWTException <ul>
4076 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4077 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4078 * </ul>
4079 */
getLineDelimiter()4080 public String getLineDelimiter() {
4081 checkWidget();
4082 return content.getLineDelimiter();
4083 }
4084 /**
4085 * Returns the line height.
4086 * <p>
4087 * Note: this API should not be used if a StyleRange attribute causes lines to
4088 * have different heights (i.e. different fonts, rise, etc).
4089 * </p>
4090 *
4091 * @return line height in points.
4092 * @exception SWTException <ul>
4093 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4094 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4095 * </ul>
4096 * @see #getLineHeight(int)
4097 */
getLineHeight()4098 public int getLineHeight() {
4099 checkWidget();
4100 return renderer.getLineHeight();
4101 }
4102 /**
4103 * Returns the line height at the given offset.
4104 *
4105 * @param offset the offset
4106 *
4107 * @return line height in points
4108 *
4109 * @exception SWTException <ul>
4110 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4111 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4112 * </ul>
4113 * @exception IllegalArgumentException <ul>
4114 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
4115 * </ul>
4116 *
4117 * @since 3.2
4118 */
getLineHeight(int offset)4119 public int getLineHeight(int offset) {
4120 checkWidget();
4121 if (!(0 <= offset && offset <= content.getCharCount())) {
4122 SWT.error(SWT.ERROR_INVALID_RANGE);
4123 }
4124 if (isFixedLineHeight()) {
4125 return renderer.getLineHeight();
4126 }
4127 int lineIndex = content.getLineAtOffset(offset);
4128 int lineOffset = content.getOffsetAtLine(lineIndex);
4129 TextLayout layout = renderer.getTextLayout(lineIndex);
4130 int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
4131 int height = layout.getLineBounds(lineInParagraph).height;
4132 renderer.disposeTextLayout(layout);
4133 return height;
4134 }
4135 /**
4136 * Returns the indentation of the line at the given index.
4137 *
4138 * @param index the index of the line
4139 *
4140 * @return the line indentation
4141 *
4142 * @exception SWTException <ul>
4143 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4144 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4145 * </ul>
4146 * @exception IllegalArgumentException <ul>
4147 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4148 * </ul>
4149 *
4150 * @see #getIndent()
4151 *
4152 * @since 3.2
4153 */
getLineIndent(int index)4154 public int getLineIndent(int index) {
4155 checkWidget();
4156 if (index < 0 || index > content.getLineCount()) {
4157 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4158 }
4159 return isListening(ST.LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
4160 }
4161 /**
4162 * Returns the vertical indentation of the line at the given index.
4163 *
4164 * @param index the index of the line
4165 *
4166 * @return the line vertical indentation
4167 *
4168 * @exception SWTException <ul>
4169 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4170 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4171 * </ul>
4172 * @exception IllegalArgumentException <ul>
4173 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4174 * </ul>
4175 *
4176 * @since 3.109
4177 */
getLineVerticalIndent(int index)4178 public int getLineVerticalIndent(int index) {
4179 checkWidget();
4180 if (index < 0 || index >= content.getLineCount()) {
4181 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4182 }
4183 return isListening(ST.LineGetStyle) ? 0 : renderer.getLineVerticalIndent(index);
4184 }
4185 /**
4186 * Returns whether the line at the given index is justified.
4187 *
4188 * @param index the index of the line
4189 *
4190 * @return whether the line is justified
4191 *
4192 * @exception SWTException <ul>
4193 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4194 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4195 * </ul>
4196 * @exception IllegalArgumentException <ul>
4197 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4198 * </ul>
4199 *
4200 * @see #getJustify()
4201 *
4202 * @since 3.2
4203 */
getLineJustify(int index)4204 public boolean getLineJustify(int index) {
4205 checkWidget();
4206 if (index < 0 || index > content.getLineCount()) {
4207 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4208 }
4209 return isListening(ST.LineGetStyle) ? false : renderer.getLineJustify(index, justify);
4210 }
4211 /**
4212 * Returns the line spacing of the widget.
4213 *
4214 * @return the line spacing
4215 *
4216 * @exception SWTException <ul>
4217 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4218 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4219 * </ul>
4220 *
4221 * @since 3.2
4222 */
getLineSpacing()4223 public int getLineSpacing() {
4224 checkWidget();
4225 return lineSpacing;
4226 }
4227 /**
4228 * Returns the line style data for the given line or null if there is
4229 * none.
4230 * <p>
4231 * If there is a LineStyleListener but it does not set any styles,
4232 * the StyledTextEvent.styles field will be initialized to an empty
4233 * array.
4234 * </p>
4235 *
4236 * @param lineOffset offset of the line start relative to the start of
4237 * the content.
4238 * @param line line to get line styles for
4239 * @return line style data for the given line. Styles may start before
4240 * line start and end after line end
4241 */
getLineStyleData(int lineOffset, String line)4242 StyledTextEvent getLineStyleData(int lineOffset, String line) {
4243 return sendLineEvent(ST.LineGetStyle, lineOffset, line);
4244 }
4245 /**
4246 * Returns the top SWT logical point, relative to the client area, of a given line.
4247 * Clamps out of ranges index.
4248 *
4249 * @param lineIndex the line index, the max value is lineCount. If
4250 * lineIndex == lineCount it returns the bottom SWT logical point of the last line.
4251 * It means this function can be used to retrieve the bottom SWT logical point of any line.
4252 *
4253 * @return the top SWT logical point of a given line index
4254 *
4255 * @since 3.2
4256 */
getLinePixel(int lineIndex)4257 public int getLinePixel(int lineIndex) {
4258 checkWidget();
4259 int lineCount = content.getLineCount();
4260 lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
4261 if (isFixedLineHeight()) {
4262 int lineHeight = renderer.getLineHeight();
4263 return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
4264 }
4265 if (lineIndex == topIndex) return topIndexY + topMargin;
4266 int height = topIndexY;
4267 if (lineIndex > topIndex) {
4268 for (int i = topIndex; i < lineIndex; i++) {
4269 height += renderer.getLineHeight(i);
4270 }
4271 } else {
4272 for (int i = topIndex - 1; i >= lineIndex; i--) {
4273 height -= renderer.getLineHeight(i);
4274 }
4275 }
4276 return height + topMargin;
4277 }
4278 /**
4279 * Returns the line index for a y, relative to the client area.
4280 * The line index returned is always in the range 0..lineCount - 1.
4281 *
4282 * @param y the y-coordinate point
4283 *
4284 * @return the line index for a given y-coordinate point
4285 *
4286 * @since 3.2
4287 */
getLineIndex(int y)4288 public int getLineIndex(int y) {
4289 checkWidget();
4290 y -= topMargin;
4291 if (isFixedLineHeight()) {
4292 int lineHeight = renderer.getLineHeight();
4293 int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
4294 int lineCount = content.getLineCount();
4295 lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
4296 return lineIndex;
4297 }
4298 if (y == topIndexY) return topIndex;
4299 int line = topIndex;
4300 if (y < topIndexY) {
4301 while (y < topIndexY && line > 0) {
4302 y += renderer.getLineHeight(--line);
4303 }
4304 } else {
4305 int lineCount = content.getLineCount();
4306 int lineHeight = renderer.getLineHeight(line);
4307 while (y - lineHeight >= topIndexY && line < lineCount - 1) {
4308 y -= lineHeight;
4309 lineHeight = renderer.getLineHeight(++line);
4310 }
4311 }
4312 return line;
4313 }
4314 /**
4315 * Returns the tab stops of the line at the given <code>index</code>.
4316 *
4317 * @param index the index of the line
4318 *
4319 * @return the tab stops for the line
4320 *
4321 * @exception SWTException <ul>
4322 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4323 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4324 * </ul>
4325 * @exception IllegalArgumentException <ul>
4326 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4327 * </ul>
4328 *
4329 * @see #getTabStops()
4330 *
4331 * @since 3.6
4332 */
getLineTabStops(int index)4333 public int[] getLineTabStops(int index) {
4334 checkWidget();
4335 if (index < 0 || index > content.getLineCount()) {
4336 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4337 }
4338 if (isListening(ST.LineGetStyle)) return null;
4339 int[] tabs = renderer.getLineTabStops(index, null);
4340 if (tabs == null) tabs = this.tabs;
4341 if (tabs == null) return new int [] {renderer.tabWidth};
4342 int[] result = new int[tabs.length];
4343 System.arraycopy(tabs, 0, result, 0, tabs.length);
4344 return result;
4345 }
4346 /**
4347 * Returns the wrap indentation of the line at the given <code>index</code>.
4348 *
4349 * @param index the index of the line
4350 *
4351 * @return the wrap indentation
4352 *
4353 * @exception SWTException <ul>
4354 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4355 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4356 * </ul>
4357 * @exception IllegalArgumentException <ul>
4358 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4359 * </ul>
4360 *
4361 * @see #getWrapIndent()
4362 *
4363 * @since 3.6
4364 */
getLineWrapIndent(int index)4365 public int getLineWrapIndent(int index) {
4366 checkWidget();
4367 if (index < 0 || index > content.getLineCount()) {
4368 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4369 }
4370 return isListening(ST.LineGetStyle) ? 0 : renderer.getLineWrapIndent(index, wrapIndent);
4371 }
4372 /**
4373 * Returns the left margin.
4374 *
4375 * @return the left margin.
4376 * @exception SWTException <ul>
4377 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4378 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4379 * </ul>
4380 *
4381 * @since 3.5
4382 */
getLeftMargin()4383 public int getLeftMargin() {
4384 checkWidget();
4385 return leftMargin - alignmentMargin;
4386 }
4387 /**
4388 * Returns the x, y location of the upper left corner of the character
4389 * bounding box at the specified offset in the text. The point is
4390 * relative to the upper left corner of the widget client area.
4391 *
4392 * @param offset offset relative to the start of the content.
4393 * 0 <= offset <= getCharCount()
4394 * @return x, y location of the upper left corner of the character
4395 * bounding box at the specified offset in the text.
4396 * @exception SWTException <ul>
4397 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4398 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4399 * </ul>
4400 * @exception IllegalArgumentException <ul>
4401 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
4402 * </ul>
4403 */
getLocationAtOffset(int offset)4404 public Point getLocationAtOffset(int offset) {
4405 checkWidget();
4406 if (offset < 0 || offset > getCharCount()) {
4407 SWT.error(SWT.ERROR_INVALID_RANGE);
4408 }
4409 return getPointAtOffset(offset);
4410 }
4411 /**
4412 * Returns <code>true</code> if the mouse navigator is enabled.
4413 * When mouse navigator is enabled, the user can navigate through the widget by pressing the middle button and moving the cursor
4414 *
4415 * @return the mouse navigator's enabled state
4416 *
4417 * @exception SWTException <ul>
4418 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4419 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4420 * </ul>
4421 *
4422 * @see #getEnabled
4423 * @since 3.110
4424 */
getMouseNavigatorEnabled()4425 public boolean getMouseNavigatorEnabled () {
4426 checkWidget ();
4427 return mouseNavigator != null;
4428 }
4429 /**
4430 * Returns the character offset of the first character of the given line.
4431 *
4432 * @param lineIndex index of the line, 0 based relative to the first
4433 * line in the content. 0 <= lineIndex < getLineCount(), except
4434 * lineIndex may always be 0
4435 * @return offset offset of the first character of the line, relative to
4436 * the beginning of the document. The first character of the document is
4437 * at offset 0.
4438 * When there are not any lines, getOffsetAtLine(0) is a valid call that
4439 * answers 0.
4440 * @exception SWTException <ul>
4441 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4442 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4443 * </ul>
4444 * @exception IllegalArgumentException <ul>
4445 * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
4446 * </ul>
4447 * @since 2.0
4448 */
getOffsetAtLine(int lineIndex)4449 public int getOffsetAtLine(int lineIndex) {
4450 checkWidget();
4451 if (lineIndex < 0 ||
4452 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
4453 SWT.error(SWT.ERROR_INVALID_RANGE);
4454 }
4455 return content.getOffsetAtLine(lineIndex);
4456 }
4457 /**
4458 * Returns the offset of the character at the given location relative
4459 * to the first character in the document.
4460 * <p>
4461 * The return value reflects the character offset that the caret will
4462 * be placed at if a mouse click occurred at the specified location.
4463 * If the x coordinate of the location is beyond the center of a character
4464 * the returned offset will be behind the character.
4465 * </p>
4466 *
4467 * @param point the origin of character bounding box relative to
4468 * the origin of the widget client area.
4469 * @return offset of the character at the given location relative
4470 * to the first character in the document.
4471 * @exception SWTException <ul>
4472 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4473 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4474 * </ul>
4475 * @exception IllegalArgumentException <ul>
4476 * <li>ERROR_NULL_ARGUMENT when point is null</li>
4477 * <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
4478 * </ul>
4479 *
4480 * @deprecated Use {@link #getOffsetAtPoint(Point)} instead for better performance
4481 */
4482 @Deprecated
getOffsetAtLocation(Point point)4483 public int getOffsetAtLocation(Point point) {
4484 checkWidget();
4485 if (point == null) {
4486 SWT.error(SWT.ERROR_NULL_ARGUMENT);
4487 }
4488 int[] trailing = new int[1];
4489 int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
4490 if (offset == -1) {
4491 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4492 }
4493 return offset + trailing[0];
4494 }
4495
4496 /**
4497 * Returns the offset of the character at the given point relative
4498 * to the first character in the document.
4499 * <p>
4500 * The return value reflects the character offset that the caret will
4501 * be placed at if a mouse click occurred at the specified point.
4502 * If the x coordinate of the point is beyond the center of a character
4503 * the returned offset will be behind the character.
4504 * </p>
4505 * Note: This method is functionally similar to {@link #getOffsetAtLocation(Point)} except that
4506 * it does not throw an exception when no character is found and thus performs faster.
4507 *
4508 * @param point the origin of character bounding box relative to
4509 * the origin of the widget client area.
4510 * @return offset of the character at the given point relative
4511 * to the first character in the document.
4512 * -1 when there is no character at the specified location.
4513 * @exception SWTException <ul>
4514 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4515 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4516 * </ul>
4517 * @exception IllegalArgumentException <ul>
4518 * <li>ERROR_NULL_ARGUMENT when point is <code>null</code></li>
4519 * </ul>
4520 *
4521 * @since 3.107
4522 */
getOffsetAtPoint(Point point)4523 public int getOffsetAtPoint(Point point) {
4524 checkWidget();
4525 if (point == null) {
4526 SWT.error(SWT.ERROR_NULL_ARGUMENT);
4527 }
4528 int[] trailing = new int[1];
4529 int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
4530 return offset != -1 ? offset + trailing[0] : -1;
4531 }
4532
getOffsetAtPoint(int x, int y, int[] alignment)4533 int getOffsetAtPoint(int x, int y, int[] alignment) {
4534 int lineIndex = getLineIndex(y);
4535 y -= getLinePixel(lineIndex);
4536 return getOffsetAtPoint(x, y, lineIndex, alignment);
4537 }
getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment)4538 int getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment) {
4539 TextLayout layout = renderer.getTextLayout(lineIndex);
4540 x += horizontalScrollOffset - leftMargin;
4541 int[] trailing = new int[1];
4542 int offsetInLine = layout.getOffset(x, y, trailing);
4543 if (alignment != null) alignment[0] = OFFSET_LEADING;
4544 if (trailing[0] != 0) {
4545 int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
4546 int lineStart = layout.getLineOffsets()[lineInParagraph];
4547 if (offsetInLine + trailing[0] == lineStart) {
4548 offsetInLine += trailing[0];
4549 if (alignment != null) alignment[0] = PREVIOUS_OFFSET_TRAILING;
4550 } else {
4551 String line = content.getLine(lineIndex);
4552 int level = 0;
4553 if (alignment != null) {
4554 int offset = offsetInLine;
4555 while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
4556 if (offset == 0 && Character.isDigit(line.charAt(offset))) {
4557 level = isMirrored() ? 1 : 0;
4558 } else {
4559 level = layout.getLevel(offset) & 0x1;
4560 }
4561 }
4562 offsetInLine += trailing[0];
4563 if (alignment != null) {
4564 int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
4565 if (level != trailingLevel) {
4566 alignment[0] = PREVIOUS_OFFSET_TRAILING;
4567 } else {
4568 alignment[0] = OFFSET_LEADING;
4569 }
4570 }
4571 }
4572 }
4573 renderer.disposeTextLayout(layout);
4574 return offsetInLine + content.getOffsetAtLine(lineIndex);
4575 }
getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly)4576 int getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly) {
4577 if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
4578 return -1;
4579 }
4580 int bottomIndex = getPartialBottomIndex();
4581 int height = getLinePixel(bottomIndex + 1);
4582 if (inTextOnly && y > height) {
4583 return -1;
4584 }
4585 int lineIndex = getLineIndex(y);
4586 int lineOffset = content.getOffsetAtLine(lineIndex);
4587 TextLayout layout = renderer.getTextLayout(lineIndex);
4588 x += horizontalScrollOffset - leftMargin;
4589 y -= getLinePixel(lineIndex);
4590 int offset = layout.getOffset(x, y, trailing);
4591 Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
4592 renderer.disposeTextLayout(layout);
4593 if (inTextOnly && !(rect.x <= x && x <= rect.x + rect.width)) {
4594 return -1;
4595 }
4596 return offset + lineOffset;
4597 }
4598 /**
4599 * Returns the orientation of the receiver.
4600 *
4601 * @return the orientation style
4602 *
4603 * @exception SWTException <ul>
4604 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4605 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4606 * </ul>
4607 *
4608 * @since 2.1.2
4609 */
4610 @Override
getOrientation()4611 public int getOrientation () {
4612 return super.getOrientation ();
4613 }
4614 /**
4615 * Returns the index of the last partially visible line.
4616 *
4617 * @return index of the last partially visible line.
4618 */
getPartialBottomIndex()4619 int getPartialBottomIndex() {
4620 if (isFixedLineHeight()) {
4621 int lineHeight = renderer.getLineHeight();
4622 int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
4623 return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
4624 }
4625 return getLineIndex(clientAreaHeight - bottomMargin);
4626 }
4627 /**
4628 * Returns the index of the first partially visible line.
4629 *
4630 * @return index of the first partially visible line.
4631 */
getPartialTopIndex()4632 int getPartialTopIndex() {
4633 if (isFixedLineHeight()) {
4634 int lineHeight = renderer.getLineHeight();
4635 return getVerticalScrollOffset() / lineHeight;
4636 }
4637 return topIndexY <= 0 ? topIndex : topIndex - 1;
4638 }
4639 /**
4640 * Returns the content in the specified range using the platform line
4641 * delimiter to separate lines.
4642 *
4643 * @param writer the TextWriter to write line text into
4644 * @return the content in the specified range using the platform line
4645 * delimiter to separate lines as written by the specified TextWriter.
4646 */
getPlatformDelimitedText(TextWriter writer)4647 String getPlatformDelimitedText(TextWriter writer) {
4648 int end = writer.getStart() + writer.getCharCount();
4649 int startLine = content.getLineAtOffset(writer.getStart());
4650 int endLine = content.getLineAtOffset(end);
4651 String endLineText = content.getLine(endLine);
4652 int endLineOffset = content.getOffsetAtLine(endLine);
4653
4654 for (int i = startLine; i <= endLine; i++) {
4655 writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
4656 if (i < endLine) {
4657 writer.writeLineDelimiter(PlatformLineDelimiter);
4658 }
4659 }
4660 if (end > endLineOffset + endLineText.length()) {
4661 writer.writeLineDelimiter(PlatformLineDelimiter);
4662 }
4663 writer.close();
4664 return writer.toString();
4665 }
4666 /**
4667 * Returns all the ranges of text that have an associated StyleRange.
4668 * Returns an empty array if a LineStyleListener has been set.
4669 * Should not be called if a LineStyleListener has been set since the
4670 * listener maintains the styles.
4671 * <p>
4672 * The ranges array contains start and length pairs. Each pair refers to
4673 * the corresponding style in the styles array. For example, the pair
4674 * that starts at ranges[n] with length ranges[n+1] uses the style
4675 * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
4676 * </p>
4677 *
4678 * @return the ranges or an empty array if a LineStyleListener has been set.
4679 *
4680 * @exception SWTException <ul>
4681 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4682 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4683 * </ul>
4684 *
4685 * @since 3.2
4686 *
4687 * @see #getStyleRanges(boolean)
4688 */
getRanges()4689 public int[] getRanges() {
4690 checkWidget();
4691 if (!isListening(ST.LineGetStyle)) {
4692 int[] ranges = renderer.getRanges(0, content.getCharCount());
4693 if (ranges != null) return ranges;
4694 }
4695 return new int[0];
4696 }
4697 /**
4698 * Returns the ranges of text that have an associated StyleRange.
4699 * Returns an empty array if a LineStyleListener has been set.
4700 * Should not be called if a LineStyleListener has been set since the
4701 * listener maintains the styles.
4702 * <p>
4703 * The ranges array contains start and length pairs. Each pair refers to
4704 * the corresponding style in the styles array. For example, the pair
4705 * that starts at ranges[n] with length ranges[n+1] uses the style
4706 * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
4707 * </p>
4708 *
4709 * @param start the start offset of the style ranges to return
4710 * @param length the number of style ranges to return
4711 *
4712 * @return the ranges or an empty array if a LineStyleListener has been set.
4713 *
4714 * @exception SWTException <ul>
4715 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4716 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4717 * </ul>
4718 * @exception IllegalArgumentException <ul>
4719 * <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
4720 * </ul>
4721 *
4722 * @since 3.2
4723 *
4724 * @see #getStyleRanges(int, int, boolean)
4725 */
getRanges(int start, int length)4726 public int[] getRanges(int start, int length) {
4727 checkWidget();
4728 int contentLength = getCharCount();
4729 int end = start + length;
4730 if (start > end || start < 0 || end > contentLength) {
4731 SWT.error(SWT.ERROR_INVALID_RANGE);
4732 }
4733 if (!isListening(ST.LineGetStyle)) {
4734 int[] ranges = renderer.getRanges(start, length);
4735 if (ranges != null) return ranges;
4736 }
4737 return new int[0];
4738 }
4739 /**
4740 * Returns the right margin.
4741 *
4742 * @return the right margin.
4743 * @exception SWTException <ul>
4744 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4745 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4746 * </ul>
4747 *
4748 * @since 3.5
4749 */
getRightMargin()4750 public int getRightMargin() {
4751 checkWidget();
4752 return rightMargin;
4753 }
4754 /**
4755 * Returns the selection.
4756 * <p>
4757 * Text selections are specified in terms of caret positions. In a text
4758 * widget that contains N characters, there are N+1 caret positions,
4759 * ranging from 0..N
4760 * </p>
4761 *
4762 * @return start and end of the selection, x is the offset of the first
4763 * selected character, y is the offset after the last selected character.
4764 * The selection values returned are visual (i.e., x will always always be
4765 * <= y). To determine if a selection is right-to-left (RtoL) vs. left-to-right
4766 * (LtoR), compare the caretOffset to the start and end of the selection
4767 * (e.g., caretOffset == start of selection implies that the selection is RtoL).
4768 * @see #getSelectionRange
4769 * @exception SWTException <ul>
4770 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4771 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4772 * </ul>
4773 */
getSelection()4774 public Point getSelection() {
4775 checkWidget();
4776 return new Point(selection.x, selection.y);
4777 }
4778 /**
4779 * Returns the selection.
4780 *
4781 * @return start and length of the selection, x is the offset of the
4782 * first selected character, relative to the first character of the
4783 * widget content. y is the length of the selection.
4784 * The selection values returned are visual (i.e., length will always always be
4785 * positive). To determine if a selection is right-to-left (RtoL) vs. left-to-right
4786 * (LtoR), compare the caretOffset to the start and end of the selection
4787 * (e.g., caretOffset == start of selection implies that the selection is RtoL).
4788 * @exception SWTException <ul>
4789 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4790 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4791 * </ul>
4792 */
getSelectionRange()4793 public Point getSelectionRange() {
4794 checkWidget();
4795 return new Point(selection.x, selection.y - selection.x);
4796 }
4797 /**
4798 * Returns the ranges of text that are inside the block selection rectangle.
4799 * <p>
4800 * The ranges array contains start and length pairs. When the receiver is not
4801 * in block selection mode the return arrays contains the start and length of
4802 * the regular selection.
4803 *
4804 * @return the ranges array
4805 *
4806 * @exception SWTException <ul>
4807 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4808 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4809 * </ul>
4810 *
4811 * @since 3.5
4812 */
getSelectionRanges()4813 public int[] getSelectionRanges() {
4814 checkWidget();
4815 if (blockSelection && blockXLocation != -1) {
4816 Rectangle rect = getBlockSelectionPosition();
4817 int firstLine = rect.y;
4818 int lastLine = rect.height;
4819 int left = rect.x;
4820 int right = rect.width;
4821 int[] ranges = new int[(lastLine - firstLine + 1) * 2];
4822 int index = 0;
4823 for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
4824 int start = getOffsetAtPoint(left, 0, lineIndex, null);
4825 int end = getOffsetAtPoint(right, 0, lineIndex, null);
4826 if (start > end) {
4827 int temp = start;
4828 start = end;
4829 end = temp;
4830 }
4831 ranges[index++] = start;
4832 ranges[index++] = end - start;
4833 }
4834 return ranges;
4835 }
4836 return new int[] {selection.x, selection.y - selection.x};
4837 }
4838 /**
4839 * Returns the receiver's selection background color.
4840 *
4841 * @return the selection background color
4842 *
4843 * @exception SWTException <ul>
4844 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4845 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4846 * </ul>
4847 * @since 2.1
4848 */
getSelectionBackground()4849 public Color getSelectionBackground() {
4850 checkWidget();
4851 if (selectionBackground == null) {
4852 return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
4853 }
4854 return selectionBackground;
4855 }
4856 /**
4857 * Gets the number of selected characters.
4858 *
4859 * @return the number of selected characters.
4860 * @exception SWTException <ul>
4861 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4862 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4863 * </ul>
4864 */
getSelectionCount()4865 public int getSelectionCount() {
4866 checkWidget();
4867 if (blockSelection && blockXLocation != -1) {
4868 return getBlockSelectionText(content.getLineDelimiter()).length();
4869 }
4870 return getSelectionRange().y;
4871 }
4872 /**
4873 * Returns the receiver's selection foreground color.
4874 *
4875 * @return the selection foreground color
4876 *
4877 * @exception SWTException <ul>
4878 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4879 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4880 * </ul>
4881 * @since 2.1
4882 */
getSelectionForeground()4883 public Color getSelectionForeground() {
4884 checkWidget();
4885 if (selectionForeground == null) {
4886 return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
4887 }
4888 return selectionForeground;
4889 }
4890 /**
4891 * Returns the selected text.
4892 *
4893 * @return selected text, or an empty String if there is no selection.
4894 * @exception SWTException <ul>
4895 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4896 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4897 * </ul>
4898 */
getSelectionText()4899 public String getSelectionText() {
4900 checkWidget();
4901 if (blockSelection && blockXLocation != -1) {
4902 return getBlockSelectionText(content.getLineDelimiter());
4903 }
4904 return content.getTextRange(selection.x, selection.y - selection.x);
4905 }
getBidiSegments(int lineOffset, String line)4906 StyledTextEvent getBidiSegments(int lineOffset, String line) {
4907 if (!isListening(ST.LineGetSegments)) {
4908 if (!bidiColoring) return null;
4909 StyledTextEvent event = new StyledTextEvent(content);
4910 event.segments = getBidiSegmentsCompatibility(line, lineOffset);
4911 return event;
4912 }
4913 StyledTextEvent event = sendLineEvent(ST.LineGetSegments, lineOffset, line);
4914 if (event == null || event.segments == null || event.segments.length == 0) return null;
4915 int lineLength = line.length();
4916 int[] segments = event.segments;
4917 if (segments[0] > lineLength) {
4918 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4919 }
4920 char[] segmentsChars = event.segmentsChars;
4921 boolean hasSegmentsChars = segmentsChars != null;
4922 for (int i = 1; i < segments.length; i++) {
4923 if ((hasSegmentsChars ? segments[i] < segments[i - 1] : segments[i] <= segments[i - 1]) || segments[i] > lineLength) {
4924 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4925 }
4926 }
4927 if (hasSegmentsChars && !visualWrap) {
4928 for (char segmentsChar : segmentsChars) {
4929 if (segmentsChar == '\n' || segmentsChar == '\r') {
4930 visualWrap = true;
4931 break;
4932 }
4933 }
4934 }
4935 return event;
4936 }
4937 /**
4938 * @see #getBidiSegments
4939 * Supports deprecated setBidiColoring API. Remove when API is removed.
4940 */
getBidiSegmentsCompatibility(String line, int lineOffset)4941 int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
4942 int lineLength = line.length();
4943 StyleRange [] styles = null;
4944 StyledTextEvent event = getLineStyleData(lineOffset, line);
4945 if (event != null) {
4946 styles = event.styles;
4947 } else {
4948 styles = renderer.getStyleRanges(lineOffset, lineLength, true);
4949 }
4950 if (styles == null || styles.length == 0) {
4951 return new int[] {0, lineLength};
4952 }
4953 int k=0, count = 1;
4954 while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) {
4955 k++;
4956 }
4957 int[] offsets = new int[(styles.length - k) * 2 + 2];
4958 for (int i = k; i < styles.length; i++) {
4959 StyleRange style = styles[i];
4960 int styleLineStart = Math.max(style.start - lineOffset, 0);
4961 int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
4962 styleLineEnd = Math.min (styleLineEnd, line.length ());
4963 if (i > 0 && count > 1 &&
4964 ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
4965 (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
4966 style.similarTo(styles[i-1])) {
4967 offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
4968 offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
4969 } else {
4970 if (styleLineStart > offsets[count - 1]) {
4971 offsets[count] = styleLineStart;
4972 count++;
4973 }
4974 offsets[count] = styleLineEnd;
4975 count++;
4976 }
4977 }
4978 // add offset for last non-colored segment in line, if any
4979 if (lineLength > offsets[count-1]) {
4980 offsets [count] = lineLength;
4981 count++;
4982 }
4983 if (count == offsets.length) {
4984 return offsets;
4985 }
4986 int [] result = new int [count];
4987 System.arraycopy (offsets, 0, result, 0, count);
4988 return result;
4989 }
4990 /**
4991 * Returns the style range at the given offset.
4992 * <p>
4993 * Returns null if a LineStyleListener has been set or if a style is not set
4994 * for the offset.
4995 * Should not be called if a LineStyleListener has been set since the
4996 * listener maintains the styles.
4997 * </p>
4998 *
4999 * @param offset the offset to return the style for.
5000 * 0 <= offset < getCharCount() must be true.
5001 * @return a StyleRange with start == offset and length == 1, indicating
5002 * the style at the given offset. null if a LineStyleListener has been set
5003 * or if a style is not set for the given offset.
5004 * @exception SWTException <ul>
5005 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5006 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5007 * </ul>
5008 * @exception IllegalArgumentException <ul>
5009 * <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
5010 * </ul>
5011 */
getStyleRangeAtOffset(int offset)5012 public StyleRange getStyleRangeAtOffset(int offset) {
5013 checkWidget();
5014 if (offset < 0 || offset >= getCharCount()) {
5015 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
5016 }
5017 if (!isListening(ST.LineGetStyle)) {
5018 StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
5019 if (ranges != null) return ranges[0];
5020 }
5021 return null;
5022 }
5023 /**
5024 * Returns the styles.
5025 * <p>
5026 * Returns an empty array if a LineStyleListener has been set.
5027 * Should not be called if a LineStyleListener has been set since the
5028 * listener maintains the styles.
5029 * </p><p>
5030 * Note: Because a StyleRange includes the start and length, the
5031 * same instance cannot occur multiple times in the array of styles.
5032 * If the same style attributes, such as font and color, occur in
5033 * multiple StyleRanges, <code>getStyleRanges(boolean)</code>
5034 * can be used to get the styles without the ranges.
5035 * </p>
5036 *
5037 * @return the styles or an empty array if a LineStyleListener has been set.
5038 *
5039 * @exception SWTException <ul>
5040 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5041 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5042 * </ul>
5043 *
5044 * @see #getStyleRanges(boolean)
5045 */
getStyleRanges()5046 public StyleRange[] getStyleRanges() {
5047 checkWidget();
5048 return getStyleRanges(0, content.getCharCount(), true);
5049 }
5050 /**
5051 * Returns the styles.
5052 * <p>
5053 * Returns an empty array if a LineStyleListener has been set.
5054 * Should not be called if a LineStyleListener has been set since the
5055 * listener maintains the styles.
5056 * </p><p>
5057 * Note: When <code>includeRanges</code> is true, the start and length
5058 * fields of each StyleRange will be valid, however the StyleRange
5059 * objects may need to be cloned. When <code>includeRanges</code> is
5060 * false, <code>getRanges(int, int)</code> can be used to get the
5061 * associated ranges.
5062 * </p>
5063 *
5064 * @param includeRanges whether the start and length field of the StyleRanges should be set.
5065 *
5066 * @return the styles or an empty array if a LineStyleListener has been set.
5067 *
5068 * @exception SWTException <ul>
5069 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5070 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5071 * </ul>
5072 *
5073 * @since 3.2
5074 *
5075 * @see #getRanges(int, int)
5076 * @see #setStyleRanges(int[], StyleRange[])
5077 */
getStyleRanges(boolean includeRanges)5078 public StyleRange[] getStyleRanges(boolean includeRanges) {
5079 checkWidget();
5080 return getStyleRanges(0, content.getCharCount(), includeRanges);
5081 }
5082 /**
5083 * Returns the styles for the given text range.
5084 * <p>
5085 * Returns an empty array if a LineStyleListener has been set.
5086 * Should not be called if a LineStyleListener has been set since the
5087 * listener maintains the styles.
5088 * </p><p>
5089 * Note: Because the StyleRange includes the start and length, the
5090 * same instance cannot occur multiple times in the array of styles.
5091 * If the same style attributes, such as font and color, occur in
5092 * multiple StyleRanges, <code>getStyleRanges(int, int, boolean)</code>
5093 * can be used to get the styles without the ranges.
5094 * </p>
5095 * @param start the start offset of the style ranges to return
5096 * @param length the number of style ranges to return
5097 *
5098 * @return the styles or an empty array if a LineStyleListener has
5099 * been set. The returned styles will reflect the given range. The first
5100 * returned <code>StyleRange</code> will have a starting offset >= start
5101 * and the last returned <code>StyleRange</code> will have an ending
5102 * offset <= start + length - 1
5103 *
5104 * @exception SWTException <ul>
5105 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5106 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5107 * </ul>
5108 * @exception IllegalArgumentException <ul>
5109 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5110 * </ul>
5111 *
5112 * @see #getStyleRanges(int, int, boolean)
5113 *
5114 * @since 3.0
5115 */
getStyleRanges(int start, int length)5116 public StyleRange[] getStyleRanges(int start, int length) {
5117 checkWidget();
5118 return getStyleRanges(start, length, true);
5119 }
5120 /**
5121 * Returns the styles for the given text range.
5122 * <p>
5123 * Returns an empty array if a LineStyleListener has been set.
5124 * Should not be called if a LineStyleListener has been set since the
5125 * listener maintains the styles.
5126 * </p><p>
5127 * Note: When <code>includeRanges</code> is true, the start and length
5128 * fields of each StyleRange will be valid, however the StyleRange
5129 * objects may need to be cloned. When <code>includeRanges</code> is
5130 * false, <code>getRanges(int, int)</code> can be used to get the
5131 * associated ranges.
5132 * </p>
5133 *
5134 * @param start the start offset of the style ranges to return
5135 * @param length the number of style ranges to return
5136 * @param includeRanges whether the start and length field of the StyleRanges should be set.
5137 *
5138 * @return the styles or an empty array if a LineStyleListener has
5139 * been set. The returned styles will reflect the given range. The first
5140 * returned <code>StyleRange</code> will have a starting offset >= start
5141 * and the last returned <code>StyleRange</code> will have an ending
5142 * offset >= start + length - 1
5143 *
5144 * @exception SWTException <ul>
5145 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5146 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5147 * </ul>
5148 * @exception IllegalArgumentException <ul>
5149 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5150 * </ul>
5151 *
5152 * @since 3.2
5153 *
5154 * @see #getRanges(int, int)
5155 * @see #setStyleRanges(int[], StyleRange[])
5156 */
getStyleRanges(int start, int length, boolean includeRanges)5157 public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) {
5158 checkWidget();
5159 int contentLength = getCharCount();
5160 int end = start + length;
5161 if (start > end || start < 0 || end > contentLength) {
5162 SWT.error(SWT.ERROR_INVALID_RANGE);
5163 }
5164 if (!isListening(ST.LineGetStyle)) {
5165 StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
5166 if (ranges != null) return ranges;
5167 }
5168 return new StyleRange[0];
5169 }
5170 /**
5171 * Returns the tab width measured in characters.
5172 *
5173 * @return tab width measured in characters
5174 * @exception SWTException <ul>
5175 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5176 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5177 * </ul>
5178 *
5179 * @see #getTabStops()
5180 */
getTabs()5181 public int getTabs() {
5182 checkWidget();
5183 return tabLength;
5184 }
5185
5186 /**
5187 * Returns the tab list of the receiver.
5188 *
5189 * @return the tab list
5190 * @exception SWTException <ul>
5191 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5192 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5193 * </ul>
5194 *
5195 * @since 3.6
5196 */
getTabStops()5197 public int[] getTabStops() {
5198 checkWidget();
5199 if (tabs == null) return new int [] {renderer.tabWidth};
5200 int[] result = new int[tabs.length];
5201 System.arraycopy(tabs, 0, result, 0, tabs.length);
5202 return result;
5203 }
5204
5205 /**
5206 * Returns a copy of the widget content.
5207 *
5208 * @return copy of the widget content
5209 * @exception SWTException <ul>
5210 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5211 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5212 * </ul>
5213 */
getText()5214 public String getText() {
5215 checkWidget();
5216 return content.getTextRange(0, getCharCount());
5217 }
5218 /**
5219 * Returns the widget content between the two offsets.
5220 *
5221 * @param start offset of the first character in the returned String
5222 * @param end offset of the last character in the returned String
5223 * @return widget content starting at start and ending at end
5224 * @see #getTextRange(int,int)
5225 * @exception SWTException <ul>
5226 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5227 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5228 * </ul>
5229 * @exception IllegalArgumentException <ul>
5230 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5231 * </ul>
5232 */
getText(int start, int end)5233 public String getText(int start, int end) {
5234 checkWidget();
5235 int contentLength = getCharCount();
5236 if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
5237 SWT.error(SWT.ERROR_INVALID_RANGE);
5238 }
5239 return content.getTextRange(start, end - start + 1);
5240 }
5241 /**
5242 * Returns the smallest bounding rectangle that includes the characters between two offsets.
5243 *
5244 * @param start offset of the first character included in the bounding box
5245 * @param end offset of the last character included in the bounding box
5246 * @return bounding box of the text between start and end
5247 * @exception SWTException <ul>
5248 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5249 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5250 * </ul>
5251 * @exception IllegalArgumentException <ul>
5252 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5253 * </ul>
5254 * @since 3.1
5255 */
getTextBounds(int start, int end)5256 public Rectangle getTextBounds(int start, int end) {
5257 checkWidget();
5258 int contentLength = getCharCount();
5259 if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
5260 SWT.error(SWT.ERROR_INVALID_RANGE);
5261 }
5262 int lineStart = content.getLineAtOffset(start);
5263 int lineEnd = content.getLineAtOffset(end);
5264 Rectangle rect;
5265 int y = getLinePixel(lineStart);
5266 int height = 0;
5267 int left = 0x7fffffff, right = 0;
5268 for (int i = lineStart; i <= lineEnd; i++) {
5269 int lineOffset = content.getOffsetAtLine(i);
5270 TextLayout layout = renderer.getTextLayout(i);
5271 int length = layout.getText().length();
5272 if (length > 0) {
5273 if (i == lineStart) {
5274 if (i == lineEnd) {
5275 rect = layout.getBounds(start - lineOffset, end - lineOffset);
5276 } else {
5277 rect = layout.getBounds(start - lineOffset, length);
5278 }
5279 y += rect.y;
5280 } else if (i == lineEnd) {
5281 rect = layout.getBounds(0, end - lineOffset);
5282 } else {
5283 rect = layout.getBounds();
5284 }
5285 left = Math.min(left, rect.x);
5286 right = Math.max(right, rect.x + rect.width);
5287 height += rect.height;
5288 } else {
5289 height += renderer.getLineHeight();
5290 }
5291 renderer.disposeTextLayout(layout);
5292 }
5293 rect = new Rectangle (left, y, right-left, height);
5294 rect.x += leftMargin - horizontalScrollOffset;
5295 return rect;
5296 }
5297 /**
5298 * Returns the widget content starting at start for length characters.
5299 *
5300 * @param start offset of the first character in the returned String
5301 * @param length number of characters to return
5302 * @return widget content starting at start and extending length characters.
5303 * @exception SWTException <ul>
5304 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5305 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5306 * </ul>
5307 * @exception IllegalArgumentException <ul>
5308 * <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
5309 * </ul>
5310 */
getTextRange(int start, int length)5311 public String getTextRange(int start, int length) {
5312 checkWidget();
5313 int contentLength = getCharCount();
5314 int end = start + length;
5315 if (start > end || start < 0 || end > contentLength) {
5316 SWT.error(SWT.ERROR_INVALID_RANGE);
5317 }
5318 return content.getTextRange(start, length);
5319 }
5320 /**
5321 * Returns the maximum number of characters that the receiver is capable of holding.
5322 *
5323 * @return the text limit
5324 *
5325 * @exception SWTException <ul>
5326 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5327 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5328 * </ul>
5329 */
getTextLimit()5330 public int getTextLimit() {
5331 checkWidget();
5332 return textLimit;
5333 }
5334 /**
5335 * Gets the top index.
5336 * <p>
5337 * The top index is the index of the fully visible line that is currently
5338 * at the top of the widget or the topmost partially visible line if no line is fully visible.
5339 * The top index changes when the widget is scrolled. Indexing is zero based.
5340 * </p>
5341 *
5342 * @return the index of the top line
5343 * @exception SWTException <ul>
5344 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5345 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5346 * </ul>
5347 */
getTopIndex()5348 public int getTopIndex() {
5349 checkWidget();
5350 return topIndex;
5351 }
5352 /**
5353 * Returns the top margin.
5354 *
5355 * @return the top margin.
5356 * @exception SWTException <ul>
5357 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5358 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5359 * </ul>
5360 *
5361 * @since 3.5
5362 */
getTopMargin()5363 public int getTopMargin() {
5364 checkWidget();
5365 return topMargin;
5366 }
5367 /**
5368 * Gets the top SWT logical point.
5369 * <p>
5370 * The top point is the SWT logical point position of the line that is
5371 * currently at the top of the widget. The text widget can be scrolled by points
5372 * by dragging the scroll thumb so that a partial line may be displayed at the top
5373 * the widget. The top point changes when the widget is scrolled. The top point
5374 * does not include the widget trimming.
5375 * </p>
5376 *
5377 * @return SWT logical point position of the top line
5378 * @exception SWTException <ul>
5379 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5380 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5381 * </ul>
5382 */
getTopPixel()5383 public int getTopPixel() {
5384 checkWidget();
5385 return getVerticalScrollOffset();
5386 }
5387 /**
5388 * Returns the vertical scroll increment.
5389 *
5390 * @return vertical scroll increment.
5391 */
getVerticalIncrement()5392 int getVerticalIncrement() {
5393 return renderer.getLineHeight();
5394 }
getVerticalScrollOffset()5395 int getVerticalScrollOffset() {
5396 if (verticalScrollOffset == -1) {
5397 renderer.calculate(0, topIndex);
5398 int height = 0;
5399 for (int i = 0; i < topIndex; i++) {
5400 height += renderer.getCachedLineHeight(i);
5401 }
5402 height -= topIndexY;
5403 verticalScrollOffset = height;
5404 }
5405 return verticalScrollOffset;
5406 }
getVisualLineIndex(TextLayout layout, int offsetInLine)5407 int getVisualLineIndex(TextLayout layout, int offsetInLine) {
5408 int lineIndex = layout.getLineIndex(offsetInLine);
5409 int[] offsets = layout.getLineOffsets();
5410 Caret caret = getCaret();
5411 if (caret != null && lineIndex != 0 && offsetInLine == offsets[lineIndex]) {
5412 int lineY = layout.getLineBounds(lineIndex).y;
5413 int caretY = caret.getLocation().y - getLinePixel(getCaretLine());
5414 if (lineY > caretY) lineIndex--;
5415 caretAlignment = OFFSET_LEADING;
5416 }
5417 return lineIndex;
5418 }
getCaretDirection()5419 int getCaretDirection() {
5420 if (!isBidiCaret()) return SWT.DEFAULT;
5421 if (ime.getCompositionOffset() != -1) return SWT.DEFAULT;
5422 if (!updateCaretDirection && caretDirection != SWT.NULL) return caretDirection;
5423 updateCaretDirection = false;
5424 int caretLine = getCaretLine();
5425 int lineOffset = content.getOffsetAtLine(caretLine);
5426 String line = content.getLine(caretLine);
5427 int offset = caretOffset - lineOffset;
5428 int lineLength = line.length();
5429 if (lineLength == 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT;
5430 if (caretAlignment == PREVIOUS_OFFSET_TRAILING && offset > 0) offset--;
5431 if (offset == lineLength && offset > 0) offset--;
5432 while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
5433 if (offset == 0 && Character.isDigit(line.charAt(offset))) {
5434 return isMirrored() ? SWT.RIGHT : SWT.LEFT;
5435 }
5436 TextLayout layout = renderer.getTextLayout(caretLine);
5437 int level = layout.getLevel(offset);
5438 renderer.disposeTextLayout(layout);
5439 return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT;
5440 }
5441 /*
5442 * Returns the index of the line the caret is on.
5443 */
getCaretLine()5444 int getCaretLine() {
5445 return content.getLineAtOffset(caretOffset);
5446 }
getWrapWidth()5447 int getWrapWidth () {
5448 if (wordWrap && !isSingleLine()) {
5449 int width = clientAreaWidth - leftMargin - rightMargin;
5450 return width > 0 ? width : 1;
5451 }
5452 return -1;
5453 }
getWordNext(int offset, int movement)5454 int getWordNext (int offset, int movement) {
5455 return getWordNext(offset, movement, false);
5456 }
getWordNext(int offset, int movement, boolean ignoreListener)5457 int getWordNext (int offset, int movement, boolean ignoreListener) {
5458 int newOffset, lineOffset;
5459 String lineText;
5460 if (offset >= getCharCount()) {
5461 newOffset = offset;
5462 int lineIndex = content.getLineCount() - 1;
5463 lineOffset = content.getOffsetAtLine(lineIndex);
5464 lineText = content.getLine(lineIndex);
5465 } else {
5466 int lineIndex = content.getLineAtOffset(offset);
5467 lineOffset = content.getOffsetAtLine(lineIndex);
5468 lineText = content.getLine(lineIndex);
5469 int lineLength = lineText.length();
5470 if (offset >= lineOffset + lineLength) {
5471 newOffset = content.getOffsetAtLine(lineIndex + 1);
5472 } else {
5473 TextLayout layout = renderer.getTextLayout(lineIndex);
5474 newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
5475 renderer.disposeTextLayout(layout);
5476 }
5477 }
5478 if (ignoreListener) return newOffset;
5479 return sendWordBoundaryEvent(ST.WordNext, movement, offset, newOffset, lineText, lineOffset);
5480 }
getWordPrevious(int offset, int movement)5481 int getWordPrevious(int offset, int movement) {
5482 return getWordPrevious(offset, movement, false);
5483 }
getWordPrevious(int offset, int movement, boolean ignoreListener)5484 int getWordPrevious(int offset, int movement, boolean ignoreListener) {
5485 int newOffset, lineOffset;
5486 String lineText;
5487 if (offset <= 0) {
5488 newOffset = 0;
5489 int lineIndex = content.getLineAtOffset(newOffset);
5490 lineOffset = content.getOffsetAtLine(lineIndex);
5491 lineText = content.getLine(lineIndex);
5492 } else {
5493 int lineIndex = content.getLineAtOffset(offset);
5494 lineOffset = content.getOffsetAtLine(lineIndex);
5495 lineText = content.getLine(lineIndex);
5496 if (offset == lineOffset) {
5497 String nextLineText = content.getLine(lineIndex - 1);
5498 int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
5499 newOffset = nextLineOffset + nextLineText.length();
5500 } else {
5501 int layoutOffset = Math.min(offset - lineOffset, lineText.length());
5502 TextLayout layout = renderer.getTextLayout(lineIndex);
5503 newOffset = lineOffset + layout.getPreviousOffset(layoutOffset, movement);
5504 renderer.disposeTextLayout(layout);
5505 }
5506 }
5507 if (ignoreListener) return newOffset;
5508 return sendWordBoundaryEvent(ST.WordPrevious, movement, offset, newOffset, lineText, lineOffset);
5509 }
5510 /**
5511 * Returns whether the widget wraps lines.
5512 *
5513 * @return true if widget wraps lines, false otherwise
5514 * @since 2.0
5515 */
getWordWrap()5516 public boolean getWordWrap() {
5517 checkWidget();
5518 return wordWrap;
5519 }
5520 /**
5521 * Returns the wrap indentation of the widget.
5522 *
5523 * @return the wrap indentation
5524 *
5525 * @exception SWTException <ul>
5526 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5527 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5528 * </ul>
5529 *
5530 * @see #getLineWrapIndent(int)
5531 *
5532 * @since 3.6
5533 */
getWrapIndent()5534 public int getWrapIndent() {
5535 checkWidget();
5536 return wrapIndent;
5537 }
5538 /**
5539 * Returns the location of the given offset.
5540 * <p>
5541 * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
5542 * </p>
5543 *
5544 * @return location of the character at the given offset in the line.
5545 */
getPointAtOffset(int offset)5546 Point getPointAtOffset(int offset) {
5547 int lineIndex = content.getLineAtOffset(offset);
5548 String line = content.getLine(lineIndex);
5549 int lineOffset = content.getOffsetAtLine(lineIndex);
5550 int offsetInLine = Math.max (0, offset - lineOffset);
5551 int lineLength = line.length();
5552 if (lineIndex < content.getLineCount() - 1) {
5553 int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
5554 if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
5555 offsetInLine = lineLength;
5556 }
5557 }
5558 Point point;
5559 TextLayout layout = renderer.getTextLayout(lineIndex);
5560 if (lineLength != 0 && offsetInLine <= lineLength) {
5561 if (offsetInLine == lineLength) {
5562 offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
5563 point = layout.getLocation(offsetInLine, true);
5564 } else {
5565 switch (caretAlignment) {
5566 case OFFSET_LEADING:
5567 point = layout.getLocation(offsetInLine, false);
5568 break;
5569 case PREVIOUS_OFFSET_TRAILING:
5570 default:
5571 boolean lineBegin = offsetInLine == 0;
5572 // If word wrap is enabled, we should also consider offsets
5573 // of wrapped line parts as line begin and do NOT go back.
5574 // This prevents clients to jump one line higher than
5575 // expected, see bug 488172.
5576 // Respect caretAlignment at the caretOffset, unless there's
5577 // a non-empty selection, see bug 488172 comment 6.
5578 if (wordWrap && !lineBegin && (offset != caretOffset || selection.x != selection.y)) {
5579 int[] offsets = layout.getLineOffsets();
5580 for (int i : offsets) {
5581 if (i == offsetInLine) {
5582 lineBegin = true;
5583 break;
5584 }
5585 }
5586 }
5587 if (lineBegin) {
5588 point = layout.getLocation(offsetInLine, false);
5589 } else {
5590 offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
5591 point = layout.getLocation(offsetInLine, true);
5592 }
5593 break;
5594 }
5595 }
5596 } else {
5597 point = new Point(layout.getIndent(), layout.getVerticalIndent());
5598 }
5599 renderer.disposeTextLayout(layout);
5600 point.x += leftMargin - horizontalScrollOffset;
5601 point.y += getLinePixel(lineIndex);
5602 return point;
5603 }
5604 /**
5605 * Inserts a string. The old selection is replaced with the new text.
5606 *
5607 * @param string the string
5608 * @see #replaceTextRange(int,int,String)
5609 * @exception SWTException <ul>
5610 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5611 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5612 * </ul>
5613 * @exception IllegalArgumentException <ul>
5614 * <li>ERROR_NULL_ARGUMENT when string is null</li>
5615 * </ul>
5616 */
insert(String string)5617 public void insert(String string) {
5618 checkWidget();
5619 if (string == null) {
5620 SWT.error(SWT.ERROR_NULL_ARGUMENT);
5621 }
5622 if (blockSelection) {
5623 insertBlockSelectionText(string, false);
5624 } else {
5625 Point sel = getSelectionRange();
5626 replaceTextRange(sel.x, sel.y, string);
5627 }
5628 }
insertBlockSelectionText(String text, boolean fillWithSpaces)5629 int insertBlockSelectionText(String text, boolean fillWithSpaces) {
5630 int lineCount = 1;
5631 for (int i = 0; i < text.length(); i++) {
5632 char ch = text.charAt(i);
5633 if (ch == '\n' || ch == '\r') {
5634 lineCount++;
5635 if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
5636 i++;
5637 }
5638 }
5639 }
5640 String[] lines = new String[lineCount];
5641 int start = 0;
5642 lineCount = 0;
5643 for (int i = 0; i < text.length(); i++) {
5644 char ch = text.charAt(i);
5645 if (ch == '\n' || ch == '\r') {
5646 lines[lineCount++] = text.substring(start, i);
5647 if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
5648 i++;
5649 }
5650 start = i + 1;
5651 }
5652 }
5653 lines[lineCount++] = text.substring(start);
5654 if (fillWithSpaces) {
5655 int maxLength = 0;
5656 for (String line : lines) {
5657 int length = line.length();
5658 maxLength = Math.max(maxLength, length);
5659 }
5660 for (int i = 0; i < lines.length; i++) {
5661 String line = lines[i];
5662 int length = line.length();
5663 if (length < maxLength) {
5664 int numSpaces = maxLength - length;
5665 StringBuilder buffer = new StringBuilder(length + numSpaces);
5666 buffer.append(line);
5667 for (int j = 0; j < numSpaces; j++) buffer.append(' ');
5668 lines[i] = buffer.toString();
5669 }
5670 }
5671 }
5672 int firstLine, lastLine, left, right;
5673 if (blockXLocation != -1) {
5674 Rectangle rect = getBlockSelectionPosition();
5675 firstLine = rect.y;
5676 lastLine = rect.height;
5677 left = rect.x;
5678 right = rect.width;
5679 } else {
5680 firstLine = lastLine = getCaretLine();
5681 left = right = getPointAtOffset(caretOffset).x;
5682 }
5683 start = caretOffset;
5684 int caretLine = getCaretLine();
5685 int index = 0, lineIndex = firstLine;
5686 while (lineIndex <= lastLine) {
5687 String string = index < lineCount ? lines[index++] : "";
5688 int lineStart = sendTextEvent(left, right, lineIndex, string, fillWithSpaces);
5689 if (lineIndex == caretLine) start = lineStart;
5690 lineIndex++;
5691 }
5692 while (index < lineCount) {
5693 int lineStart = sendTextEvent(left, left, lineIndex, lines[index++], fillWithSpaces);
5694 if (lineIndex == caretLine) start = lineStart;
5695 lineIndex++;
5696 }
5697 return start;
5698 }
5699 void insertBlockSelectionText(char key, int action) {
5700 if (key == SWT.CR || key == SWT.LF) return;
5701 Rectangle rect = getBlockSelectionPosition();
5702 int firstLine = rect.y;
5703 int lastLine = rect.height;
5704 int left = rect.x;
5705 int right = rect.width;
5706 int[] trailing = new int[1];
5707 int offset = 0, delta = 0;
5708 String text = key != 0 ? new String(new char[] {key}) : "";
5709 int length = text.length();
5710 for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
5711 String line = content.getLine(lineIndex);
5712 int lineOffset = content.getOffsetAtLine(lineIndex);
5713 int lineEndOffset = lineOffset + line.length();
5714 int linePixel = getLinePixel(lineIndex);
5715 int start = getOffsetAtPoint(left, linePixel, trailing, true);
5716 boolean outOfLine = start == -1;
5717 if (outOfLine) {
5718 start = left < leftMargin ? lineOffset : lineEndOffset;
5719 } else {
5720 start += trailing[0];
5721 }
5722 int end = getOffsetAtPoint(right, linePixel, trailing, true);
5723 if (end == -1) {
5724 end = right < leftMargin ? lineOffset : lineEndOffset;
5725 } else {
5726 end += trailing[0];
5727 }
5728 if (start > end) {
5729 int temp = start;
5730 start = end;
5731 end = temp;
5732 }
5733 if (start == end && !outOfLine) {
5734 switch (action) {
5735 case ST.DELETE_PREVIOUS:
5736 if (start > lineOffset) start = getClusterPrevious(start, lineIndex);
5737 break;
5738 case ST.DELETE_NEXT:
5739 if (end < lineEndOffset) end = getClusterNext(end, lineIndex);
5740 break;
5741 }
5742 }
5743 if (outOfLine) {
5744 if (line.length() >= delta) {
5745 delta = line.length();
5746 offset = lineEndOffset + length;
5747 }
5748 } else {
5749 offset = start + length;
5750 delta = content.getCharCount();
5751 }
5752 Event event = new Event();
5753 event.text = text;
5754 event.start = start;
5755 event.end = end;
5756 sendKeyEvent(event);
5757 }
5758 int x = getPointAtOffset(offset).x;
5759 int verticalScrollOffset = getVerticalScrollOffset();
5760 setBlockSelectionLocation(x, blockYAnchor - verticalScrollOffset, x, blockYLocation - verticalScrollOffset, false);
5761 }
5762 /**
5763 * Creates content change listeners and set the default content model.
5764 */
5765 void installDefaultContent() {
5766 textChangeListener = new TextChangeListener() {
5767 @Override
5768 public void textChanging(TextChangingEvent event) {
5769 handleTextChanging(event);
5770 }
5771 @Override
5772 public void textChanged(TextChangedEvent event) {
5773 handleTextChanged(event);
5774 }
5775 @Override
5776 public void textSet(TextChangedEvent event) {
5777 handleTextSet(event);
5778 }
5779 };
5780 content = new DefaultContent();
5781 content.addTextChangeListener(textChangeListener);
5782 }
5783 /**
5784 * Adds event listeners
5785 */
5786 void installListeners() {
5787 ScrollBar verticalBar = getVerticalBar();
5788 ScrollBar horizontalBar = getHorizontalBar();
5789
5790 listener = event -> {
5791 switch (event.type) {
5792 case SWT.Dispose: handleDispose(event); break;
5793 case SWT.KeyDown: handleKeyDown(event); break;
5794 case SWT.KeyUp: handleKeyUp(event); break;
5795 case SWT.MenuDetect: handleMenuDetect(event); break;
5796 case SWT.MouseDown: handleMouseDown(event); break;
5797 case SWT.MouseUp: handleMouseUp(event); break;
5798 case SWT.MouseMove: handleMouseMove(event); break;
5799 case SWT.Paint: handlePaint(event); break;
5800 case SWT.Resize: handleResize(event); break;
5801 case SWT.Traverse: handleTraverse(event); break;
5802 }
5803 };
5804 addListener(SWT.Dispose, listener);
5805 addListener(SWT.KeyDown, listener);
5806 addListener(SWT.KeyUp, listener);
5807 addListener(SWT.MenuDetect, listener);
5808 addListener(SWT.MouseDown, listener);
5809 addListener(SWT.MouseUp, listener);
5810 addListener(SWT.MouseMove, listener);
5811 addListener(SWT.Paint, listener);
5812 addListener(SWT.Resize, listener);
5813 addListener(SWT.Traverse, listener);
5814 ime.addListener(SWT.ImeComposition, event -> {
5815 if (!editable) {
5816 event.doit = false;
5817 event.start = 0;
5818 event.end = 0;
5819 event.text = "";
5820 return;
5821 }
5822
5823 switch (event.detail) {
5824 case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break;
5825 case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break;
5826 case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break;
5827 }
5828 });
5829 if (verticalBar != null) {
5830 verticalBar.addListener(SWT.Selection, event -> handleVerticalScroll(event));
5831 }
5832 if (horizontalBar != null) {
5833 horizontalBar.addListener(SWT.Selection, event -> handleHorizontalScroll(event));
5834 }
5835 }
internalRedrawRange(int start, int length)5836 void internalRedrawRange(int start, int length) {
5837 if (length <= 0) return;
5838 int end = start + length;
5839 int startLine = content.getLineAtOffset(start);
5840 int endLine = content.getLineAtOffset(end);
5841 int partialBottomIndex = getPartialBottomIndex();
5842 int partialTopIndex = getPartialTopIndex();
5843 if (startLine > partialBottomIndex || endLine < partialTopIndex) {
5844 return;
5845 }
5846 if (partialTopIndex > startLine) {
5847 startLine = partialTopIndex;
5848 start = 0;
5849 } else {
5850 start -= content.getOffsetAtLine(startLine);
5851 }
5852 if (partialBottomIndex < endLine) {
5853 endLine = partialBottomIndex + 1;
5854 end = 0;
5855 } else {
5856 end -= content.getOffsetAtLine(endLine);
5857 }
5858
5859 TextLayout layout = renderer.getTextLayout(startLine);
5860 int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
5861 int[] offsets = layout.getLineOffsets();
5862 int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length()));
5863
5864 /* Redraw end of line before start line if wrapped and start offset is first char */
5865 if (isWordWrap() && startIndex > 0 && offsets[startIndex] == start) {
5866 Rectangle rect = layout.getLineBounds(startIndex - 1);
5867 rect.x = rect.width;
5868 rect.width = clientAreaWidth - rightMargin - rect.x;
5869 rect.x += lineX;
5870 rect.y += startLineY;
5871 super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5872 }
5873
5874 if (startLine == endLine) {
5875 int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
5876 if (startIndex == endIndex) {
5877 /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
5878 Rectangle rect = layout.getBounds(start, end - 1);
5879 rect.x += lineX;
5880 rect.y += startLineY;
5881 super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5882 renderer.disposeTextLayout(layout);
5883 return;
5884 }
5885 }
5886
5887 /* Redraw start line from the start offset to the end of client area */
5888 Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1);
5889 if (startRect.height == 0) {
5890 Rectangle bounds = layout.getLineBounds(startIndex);
5891 startRect.x = bounds.width;
5892 startRect.y = bounds.y;
5893 startRect.height = bounds.height;
5894 }
5895 startRect.x += lineX;
5896 startRect.y += startLineY;
5897 startRect.width = clientAreaWidth - rightMargin - startRect.x;
5898 super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);
5899
5900 /* Redraw end line from the beginning of the line to the end offset */
5901 if (startLine != endLine) {
5902 renderer.disposeTextLayout(layout);
5903 layout = renderer.getTextLayout(endLine);
5904 offsets = layout.getLineOffsets();
5905 }
5906 int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
5907 Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1);
5908 if (endRect.height == 0) {
5909 Rectangle bounds = layout.getLineBounds(endIndex);
5910 endRect.y = bounds.y;
5911 endRect.height = bounds.height;
5912 }
5913 endRect.x += lineX;
5914 endRect.y += getLinePixel(endLine);
5915 super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
5916 renderer.disposeTextLayout(layout);
5917
5918 /* Redraw all lines in between start and end line */
5919 int y = startRect.y + startRect.height;
5920 if (endRect.y > y) {
5921 super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
5922 }
5923 }
handleCompositionOffset(Event event)5924 void handleCompositionOffset (Event event) {
5925 int[] trailing = new int [1];
5926 event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
5927 event.count = trailing[0];
5928 }
handleCompositionSelection(Event event)5929 void handleCompositionSelection (Event event) {
5930 if (event.start != event.end) {
5931 int charCount = getCharCount();
5932 event.start = Math.max(0, Math.min(event.start, charCount));
5933 event.end = Math.max(0, Math.min(event.end, charCount));
5934 if (event.text != null) {
5935 setSelection(event.start, event.end);
5936 } else {
5937 event.text = getTextRange(event.start, event.end - event.start);
5938 }
5939 } else {
5940 event.start = selection.x;
5941 event.end = selection.y;
5942 event.text = getSelectionText();
5943 }
5944 }
handleCompositionChanged(Event event)5945 void handleCompositionChanged(Event event) {
5946 String text = event.text;
5947 int start = event.start;
5948 int end = event.end;
5949 int charCount = content.getCharCount();
5950 start = Math.min(start, charCount);
5951 end = Math.min(end, charCount);
5952 int length = text.length();
5953 if (length == ime.getCommitCount()) {
5954 content.replaceTextRange(start, end - start, "");
5955 setCaretOffset(ime.getCompositionOffset(), SWT.DEFAULT);
5956 caretWidth = 0;
5957 caretDirection = SWT.NULL;
5958 } else {
5959 content.replaceTextRange(start, end - start, text);
5960 int alignment = SWT.DEFAULT;
5961 if (ime.getWideCaret()) {
5962 start = ime.getCompositionOffset();
5963 int lineIndex = getCaretLine();
5964 int lineOffset = content.getOffsetAtLine(lineIndex);
5965 TextLayout layout = renderer.getTextLayout(lineIndex);
5966 caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width;
5967 renderer.disposeTextLayout(layout);
5968 alignment = OFFSET_LEADING;
5969 }
5970 setCaretOffset(ime.getCaretOffset(), alignment);
5971 }
5972 resetSelection();
5973 showCaret();
5974 }
5975 /**
5976 * Frees resources.
5977 */
handleDispose(Event event)5978 void handleDispose(Event event) {
5979 removeListener(SWT.Dispose, listener);
5980 notifyListeners(SWT.Dispose, event);
5981 event.type = SWT.None;
5982
5983 clipboard.dispose();
5984 if (renderer != null) {
5985 renderer.dispose();
5986 renderer = null;
5987 }
5988 if (content != null) {
5989 content.removeTextChangeListener(textChangeListener);
5990 content = null;
5991 }
5992 if (defaultCaret != null) {
5993 defaultCaret.dispose();
5994 defaultCaret = null;
5995 }
5996 if (leftCaretBitmap != null) {
5997 leftCaretBitmap.dispose();
5998 leftCaretBitmap = null;
5999 }
6000 if (rightCaretBitmap != null) {
6001 rightCaretBitmap.dispose();
6002 rightCaretBitmap = null;
6003 }
6004 if (isBidiCaret()) {
6005 BidiUtil.removeLanguageListener(this);
6006 }
6007 selectionBackground = null;
6008 selectionForeground = null;
6009 marginColor = null;
6010 textChangeListener = null;
6011 selection = null;
6012 doubleClickSelection = null;
6013 keyActionMap = null;
6014 background = null;
6015 foreground = null;
6016 clipboard = null;
6017 tabs = null;
6018 }
6019 /**
6020 * Scrolls the widget horizontally.
6021 */
handleHorizontalScroll(Event event)6022 void handleHorizontalScroll(Event event) {
6023 int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
6024 scrollHorizontal(scrollPixel, false);
6025 }
6026 /**
6027 * If an action has been registered for the key stroke execute the action.
6028 * Otherwise, if a character has been entered treat it as new content.
6029 *
6030 * @param event keyboard event
6031 */
handleKey(Event event)6032 void handleKey(Event event) {
6033 int action;
6034 caretAlignment = PREVIOUS_OFFSET_TRAILING;
6035 if (event.keyCode != 0) {
6036 // special key pressed (e.g., F1)
6037 action = getKeyBinding(event.keyCode | event.stateMask);
6038 } else {
6039 // character key pressed
6040 action = getKeyBinding(event.character | event.stateMask);
6041 if (action == SWT.NULL) {
6042 // see if we have a control character
6043 if ((event.stateMask & SWT.CTRL) != 0 && event.character <= 31) {
6044 // get the character from the CTRL+char sequence, the control
6045 // key subtracts 64 from the value of the key that it modifies
6046 int c = event.character + 64;
6047 action = getKeyBinding(c | event.stateMask);
6048 }
6049 }
6050 }
6051 if (action == SWT.NULL) {
6052 boolean ignore = false;
6053
6054 if (IS_MAC) {
6055 // Ignore accelerator key combinations (we do not want to
6056 // insert a character in the text in this instance).
6057 ignore = (event.stateMask & (SWT.COMMAND | SWT.CTRL)) != 0;
6058 } else {
6059 // Ignore accelerator key combinations (we do not want to
6060 // insert a character in the text in this instance). Don't
6061 // ignore CTRL+ALT combinations since that is the Alt Gr
6062 // key on some keyboards. See bug 20953.
6063 ignore = event.stateMask == SWT.ALT ||
6064 event.stateMask == SWT.CTRL ||
6065 event.stateMask == (SWT.ALT | SWT.SHIFT) ||
6066 event.stateMask == (SWT.CTRL | SWT.SHIFT);
6067 }
6068 // -ignore anything below SPACE except for line delimiter keys and tab.
6069 // -ignore DEL
6070 if (!ignore && event.character > 31 && event.character != SWT.DEL ||
6071 event.character == SWT.CR || event.character == SWT.LF ||
6072 event.character == TAB) {
6073 doContent(event.character);
6074 update();
6075 }
6076 } else {
6077 invokeAction(action);
6078 }
6079 }
6080 /**
6081 * If a VerifyKey listener exists, verify that the key that was entered
6082 * should be processed.
6083 *
6084 * @param event keyboard event
6085 */
handleKeyDown(Event event)6086 void handleKeyDown(Event event) {
6087 if (clipboardSelection == null) {
6088 clipboardSelection = new Point(selection.x, selection.y);
6089 }
6090 newOrientation = SWT.NONE;
6091 event.stateMask &= SWT.MODIFIER_MASK;
6092
6093 Event verifyEvent = new Event();
6094 verifyEvent.character = event.character;
6095 verifyEvent.keyCode = event.keyCode;
6096 verifyEvent.keyLocation = event.keyLocation;
6097 verifyEvent.stateMask = event.stateMask;
6098 verifyEvent.doit = event.doit;
6099 notifyListeners(ST.VerifyKey, verifyEvent);
6100 if (verifyEvent.doit) {
6101 if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL && event.keyCode == SWT.SHIFT && isBidiCaret()) {
6102 newOrientation = event.keyLocation == SWT.LEFT ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT;
6103 }
6104 handleKey(event);
6105 }
6106 }
6107 /**
6108 * Update the Selection Clipboard.
6109 *
6110 * @param event keyboard event
6111 */
handleKeyUp(Event event)6112 void handleKeyUp(Event event) {
6113 if (clipboardSelection != null) {
6114 if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) {
6115 copySelection(DND.SELECTION_CLIPBOARD);
6116 }
6117 }
6118 clipboardSelection = null;
6119
6120 if (newOrientation != SWT.NONE) {
6121 if (newOrientation != getOrientation()) {
6122 Event e = new Event();
6123 e.doit = true;
6124 notifyListeners(SWT.OrientationChange, e);
6125 if (e.doit) {
6126 setOrientation(newOrientation);
6127 }
6128 }
6129 newOrientation = SWT.NONE;
6130 }
6131 }
6132 /**
6133 * Update the event location for focus-based context menu triggers.
6134 *
6135 * @param event menu detect event
6136 */
handleMenuDetect(Event event)6137 void handleMenuDetect(Event event) {
6138 if (event.detail == SWT.MENU_KEYBOARD) {
6139 Point point = getDisplay().map(this, null, getPointAtOffset(caretOffset));
6140 event.x = point.x;
6141 event.y = point.y + getLineHeight(caretOffset);
6142 }
6143 }
6144 /**
6145 * Updates the caret location and selection if mouse button 1 has been
6146 * pressed.
6147 */
handleMouseDown(Event event)6148 void handleMouseDown(Event event) {
6149 //force focus (object support)
6150 forceFocus();
6151
6152 //drag detect
6153 if (dragDetect && checkDragDetect(event)) return;
6154
6155 //paste clipboard selection
6156 if (event.button == 2) {
6157 // On GTK, if mouseNavigator is enabled we have to distinguish a short middle-click (to paste content) from
6158 // a long middle-click (mouse navigation started)
6159 if (IS_GTK && mouseNavigator != null) {
6160 middleClickPressed = true;
6161 getDisplay().timerExec(200, ()->{
6162 boolean click = middleClickPressed;
6163 middleClickPressed = false;
6164 if (click && mouseNavigator !=null) {
6165 mouseNavigator.onMouseDown(event);
6166 } else {
6167 pasteOnMiddleClick(event);
6168 }
6169 });
6170 return;
6171 } else {
6172 pasteOnMiddleClick(event);
6173 }
6174 }
6175
6176 //set selection
6177 if ((event.button != 1) || (IS_MAC && (event.stateMask & SWT.MOD4) != 0)) {
6178 return;
6179 }
6180 clickCount = event.count;
6181 if (clickCount == 1) {
6182 boolean select = (event.stateMask & SWT.MOD2) != 0;
6183 doMouseLocationChange(event.x, event.y, select);
6184 } else {
6185 if (doubleClickEnabled) {
6186 boolean wordSelect = (clickCount & 1) == 0;
6187 int offset = getOffsetAtPoint(event.x, event.y, null);
6188 int lineIndex = content.getLineAtOffset(offset);
6189 int lineOffset = content.getOffsetAtLine(lineIndex);
6190 if (wordSelect) {
6191 int min = blockSelection ? lineOffset : 0;
6192 int max = blockSelection ? lineOffset + content.getLine(lineIndex).length() : content.getCharCount();
6193 int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
6194 int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END));
6195 setSelection(start, end - start, false, true);
6196 sendSelectionEvent();
6197 } else {
6198 if (blockSelection) {
6199 setBlockSelectionLocation(leftMargin, event.y, clientAreaWidth - rightMargin, event.y, true);
6200 } else {
6201 int lineEnd = content.getCharCount();
6202 if (lineIndex + 1 < content.getLineCount()) {
6203 lineEnd = content.getOffsetAtLine(lineIndex + 1);
6204 }
6205 setSelection(lineOffset, lineEnd - lineOffset, false, false);
6206 sendSelectionEvent();
6207 }
6208 }
6209 doubleClickSelection = new Point(selection.x, selection.y);
6210 showCaret();
6211 }
6212 }
6213 }
6214 /**
6215 * Updates the caret location and selection if mouse button 1 is pressed
6216 * during the mouse move.
6217 */
handleMouseMove(Event event)6218 void handleMouseMove(Event event) {
6219 if (clickCount > 0) {
6220 update();
6221 doAutoScroll(event);
6222 doMouseLocationChange(event.x, event.y, true);
6223 }
6224 if (renderer.hasLinks) {
6225 doMouseLinkCursor(event.x, event.y);
6226 }
6227 }
6228 /**
6229 * Autoscrolling ends when the mouse button is released.
6230 */
handleMouseUp(Event event)6231 void handleMouseUp(Event event) {
6232 middleClickPressed = false;
6233 clickCount = 0;
6234 endAutoScroll();
6235 if (event.button == 1) {
6236 copySelection(DND.SELECTION_CLIPBOARD);
6237 }
6238 }
6239 /**
6240 * Renders the invalidated area specified in the paint event.
6241 *
6242 * @param event paint event
6243 */
handlePaint(Event event)6244 void handlePaint(Event event) {
6245 if (event.width == 0 || event.height == 0) return;
6246 if (clientAreaWidth == 0 || clientAreaHeight == 0) return;
6247
6248 int startLine = getLineIndex(event.y);
6249 int y = getLinePixel(startLine);
6250 int endY = event.y + event.height;
6251 GC gc = event.gc;
6252 Color background = getBackground();
6253 Color foreground = getForeground();
6254 if (endY > 0) {
6255 int lineCount = isSingleLine() ? 1 : content.getLineCount();
6256 int x = leftMargin - horizontalScrollOffset;
6257 for (int i = startLine; y < endY && i < lineCount; i++) {
6258 y += renderer.drawLine(i, x, y, gc, background, foreground);
6259 }
6260 if (y < endY) {
6261 gc.setBackground(background);
6262 drawBackground(gc, 0, y, clientAreaWidth, endY - y);
6263 }
6264 }
6265 if (blockSelection && blockXLocation != -1) {
6266 gc.setBackground(getSelectionBackground());
6267 Rectangle rect = getBlockSelectionRectangle();
6268 gc.drawRectangle(rect.x, rect.y, Math.max(1, rect.width - 1), Math.max(1, rect.height - 1));
6269 gc.setAdvanced(true);
6270 if (gc.getAdvanced()) {
6271 gc.setAlpha(100);
6272 gc.fillRectangle(rect);
6273 gc.setAdvanced(false);
6274 }
6275 }
6276
6277 // fill the margin background
6278 gc.setBackground(marginColor != null ? marginColor : background);
6279 if (topMargin > 0) {
6280 drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
6281 }
6282 if (bottomMargin > 0) {
6283 drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
6284 }
6285 if (leftMargin - alignmentMargin > 0) {
6286 drawBackground(gc, 0, 0, leftMargin - alignmentMargin, clientAreaHeight);
6287 }
6288 if (rightMargin > 0) {
6289 drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
6290 }
6291 }
6292 /**
6293 * Recalculates the scroll bars. Rewraps all lines when in word
6294 * wrap mode.
6295 *
6296 * @param event resize event
6297 */
handleResize(Event event)6298 void handleResize(Event event) {
6299 int oldHeight = clientAreaHeight;
6300 int oldWidth = clientAreaWidth;
6301 Rectangle clientArea = getClientArea();
6302 clientAreaHeight = clientArea.height;
6303 clientAreaWidth = clientArea.width;
6304 if (!alwaysShowScroll && ignoreResize != 0) return;
6305
6306 redrawMargins(oldHeight, oldWidth);
6307 if (wordWrap) {
6308 if (oldWidth != clientAreaWidth) {
6309 renderer.reset(0, content.getLineCount());
6310 verticalScrollOffset = -1;
6311 renderer.calculateIdle();
6312 super.redraw();
6313 }
6314 if (oldHeight != clientAreaHeight) {
6315 if (oldHeight == 0) topIndexY = 0;
6316 setScrollBars(true);
6317 }
6318 setCaretLocation();
6319 } else {
6320 renderer.calculateClientArea();
6321 setScrollBars(true);
6322 claimRightFreeSpace();
6323 // StyledText allows any value for horizontalScrollOffset when clientArea is zero
6324 // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
6325 if (clientAreaWidth != 0) {
6326 ScrollBar horizontalBar = getHorizontalBar();
6327 if (horizontalBar != null && horizontalBar.getVisible()) {
6328 if (horizontalScrollOffset != horizontalBar.getSelection()) {
6329 horizontalBar.setSelection(horizontalScrollOffset);
6330 horizontalScrollOffset = horizontalBar.getSelection();
6331 }
6332 }
6333 }
6334 }
6335 updateCaretVisibility();
6336 claimBottomFreeSpace();
6337 setAlignment();
6338 //TODO FIX TOP INDEX DURING RESIZE
6339 // if (oldHeight != clientAreaHeight || wordWrap) {
6340 // calculateTopIndex(0);
6341 // }
6342 }
6343 /**
6344 * Updates the caret position and selection and the scroll bars to reflect
6345 * the content change.
6346 */
handleTextChanged(TextChangedEvent event)6347 void handleTextChanged(TextChangedEvent event) {
6348 int offset = ime.getCompositionOffset();
6349 if (offset != -1 && lastTextChangeStart < offset) {
6350 ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount);
6351 }
6352 int firstLine = content.getLineAtOffset(lastTextChangeStart);
6353 resetCache(firstLine, 0);
6354 if (!isFixedLineHeight() && topIndex > firstLine) {
6355 topIndex = firstLine;
6356 if (topIndex < 0) {
6357 // TODO: This logging is in place to determine why topIndex is getting set to negative values.
6358 // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
6359 System.err.println("StyledText: topIndex was " + topIndex
6360 + ", lastTextChangeStart = " + lastTextChangeStart
6361 + ", content.getClass() = " + content.getClass()
6362 );
6363 topIndex = 0;
6364 }
6365 topIndexY = 0;
6366 super.redraw();
6367 } else {
6368 int lastLine = firstLine + lastTextChangeNewLineCount;
6369 int firstLineTop = getLinePixel(firstLine);
6370 int newLastLineBottom = getLinePixel(lastLine + 1);
6371 if (lastLineBottom != newLastLineBottom) {
6372 super.redraw();
6373 } else {
6374 super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
6375 redrawLinesBullet(renderer.redrawLines);
6376 }
6377 }
6378 renderer.redrawLines = null;
6379 // update selection/caret location after styles have been changed.
6380 // otherwise any text measuring could be incorrect
6381 //
6382 // also, this needs to be done after all scrolling. Otherwise,
6383 // selection redraw would be flushed during scroll which is wrong.
6384 // in some cases new text would be drawn in scroll source area even
6385 // though the intent is to scroll it.
6386 if (!(blockSelection && blockXLocation != -1)) {
6387 updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
6388 }
6389 if (lastTextChangeReplaceLineCount > 0 || wordWrap || visualWrap) {
6390 claimBottomFreeSpace();
6391 }
6392 if (lastTextChangeReplaceCharCount > 0) {
6393 claimRightFreeSpace();
6394 }
6395
6396 sendAccessibleTextChanged(lastTextChangeStart, lastTextChangeNewCharCount, 0);
6397 lastCharCount += lastTextChangeNewCharCount;
6398 lastCharCount -= lastTextChangeReplaceCharCount;
6399 setAlignment();
6400 }
6401 /**
6402 * Updates the screen to reflect a pending content change.
6403 *
6404 * @param event .start the start offset of the change
6405 * @param event .newText text that is going to be inserted or empty String
6406 * if no text will be inserted
6407 * @param event .replaceCharCount length of text that is going to be replaced
6408 * @param event .newCharCount length of text that is going to be inserted
6409 * @param event .replaceLineCount number of lines that are going to be replaced
6410 * @param event .newLineCount number of new lines that are going to be inserted
6411 */
handleTextChanging(TextChangingEvent event)6412 void handleTextChanging(TextChangingEvent event) {
6413 if (event.replaceCharCount < 0) {
6414 event.start += event.replaceCharCount;
6415 event.replaceCharCount *= -1;
6416 }
6417 lastTextChangeStart = event.start;
6418 lastTextChangeNewLineCount = event.newLineCount;
6419 lastTextChangeNewCharCount = event.newCharCount;
6420 lastTextChangeReplaceLineCount = event.replaceLineCount;
6421 lastTextChangeReplaceCharCount = event.replaceCharCount;
6422 int lineIndex = content.getLineAtOffset(event.start);
6423 int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
6424 int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
6425 lastLineBottom = destY;
6426 if (srcY < 0 && destY < 0) {
6427 lastLineBottom += srcY - destY;
6428 verticalScrollOffset += destY - srcY;
6429 calculateTopIndex(destY - srcY);
6430 setScrollBars(true);
6431 } else {
6432 scrollText(srcY, destY);
6433 }
6434 sendAccessibleTextChanged(lastTextChangeStart, 0, lastTextChangeReplaceCharCount);
6435 renderer.textChanging(event);
6436
6437 // Update the caret offset if it is greater than the length of the content.
6438 // This is necessary since style range API may be called between the
6439 // handleTextChanging and handleTextChanged events and this API sets the
6440 // caretOffset.
6441 int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
6442 if (caretOffset > newEndOfText) setCaretOffset(newEndOfText, SWT.DEFAULT);
6443 }
6444 /**
6445 * Called when the widget content is set programmatically, overwriting
6446 * the old content. Resets the caret position, selection and scroll offsets.
6447 * Recalculates the content width and scroll bars. Redraws the widget.
6448 *
6449 * @param event text change event.
6450 */
handleTextSet(TextChangedEvent event)6451 void handleTextSet(TextChangedEvent event) {
6452 reset();
6453 int newCharCount = getCharCount();
6454 sendAccessibleTextChanged(0, newCharCount, lastCharCount);
6455 lastCharCount = newCharCount;
6456 setAlignment();
6457 }
6458 /**
6459 * Called when a traversal key is pressed.
6460 * Allow tab next traversal to occur when the widget is in single
6461 * line mode or in multi line and non-editable mode .
6462 * When in editable multi line mode we want to prevent the tab
6463 * traversal and receive the tab key event instead.
6464 *
6465 * @param event the event
6466 */
handleTraverse(Event event)6467 void handleTraverse(Event event) {
6468 switch (event.detail) {
6469 case SWT.TRAVERSE_ESCAPE:
6470 case SWT.TRAVERSE_PAGE_NEXT:
6471 case SWT.TRAVERSE_PAGE_PREVIOUS:
6472 event.doit = true;
6473 break;
6474 case SWT.TRAVERSE_RETURN:
6475 case SWT.TRAVERSE_TAB_NEXT:
6476 case SWT.TRAVERSE_TAB_PREVIOUS:
6477 if ((getStyle() & SWT.SINGLE) != 0) {
6478 event.doit = true;
6479 } else {
6480 if (!editable || (event.stateMask & SWT.MODIFIER_MASK) != 0) {
6481 event.doit = true;
6482 }
6483 }
6484 break;
6485 }
6486 }
6487 /**
6488 * Scrolls the widget vertically.
6489 */
handleVerticalScroll(Event event)6490 void handleVerticalScroll(Event event) {
6491 int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
6492 scrollVertical(scrollPixel, false);
6493 }
6494 /**
6495 * Add accessibility support for the widget.
6496 */
initializeAccessible()6497 void initializeAccessible() {
6498 acc = getAccessible();
6499
6500 accAdapter = new AccessibleAdapter() {
6501 @Override
6502 public void getName (AccessibleEvent e) {
6503 String name = null;
6504 String text = getAssociatedLabel ();
6505 if (text != null) {
6506 name = stripMnemonic (text);
6507 }
6508 e.result = name;
6509 }
6510 @Override
6511 public void getHelp(AccessibleEvent e) {
6512 e.result = getToolTipText();
6513 }
6514 @Override
6515 public void getKeyboardShortcut(AccessibleEvent e) {
6516 String shortcut = null;
6517 String text = getAssociatedLabel ();
6518 if (text != null) {
6519 char mnemonic = _findMnemonic (text);
6520 if (mnemonic != '\0') {
6521 shortcut = "Alt+"+mnemonic; //$NON-NLS-1$
6522 }
6523 }
6524 e.result = shortcut;
6525 }
6526 };
6527 acc.addAccessibleListener(accAdapter);
6528
6529 accTextExtendedAdapter = new AccessibleTextExtendedAdapter() {
6530 @Override
6531 public void getCaretOffset(AccessibleTextEvent e) {
6532 e.offset = StyledText.this.getCaretOffset();
6533 }
6534 @Override
6535 public void setCaretOffset(AccessibleTextEvent e) {
6536 StyledText.this.setCaretOffset(e.offset);
6537 e.result = ACC.OK;
6538 }
6539 @Override
6540 public void getSelectionRange(AccessibleTextEvent e) {
6541 Point selection = StyledText.this.getSelectionRange();
6542 e.offset = selection.x;
6543 e.length = selection.y;
6544 }
6545 @Override
6546 public void addSelection(AccessibleTextEvent e) {
6547 StyledText st = StyledText.this;
6548 Point point = st.getSelection();
6549 if (point.x == point.y) {
6550 int end = e.end;
6551 if (end == -1) end = st.getCharCount();
6552 st.setSelection(e.start, end);
6553 e.result = ACC.OK;
6554 }
6555 }
6556 @Override
6557 public void getSelection(AccessibleTextEvent e) {
6558 StyledText st = StyledText.this;
6559 if (st.blockSelection && st.blockXLocation != -1) {
6560 Rectangle rect = st.getBlockSelectionPosition();
6561 int lineIndex = rect.y + e.index;
6562 int linePixel = st.getLinePixel(lineIndex);
6563 e.ranges = getRanges(rect.x, linePixel, rect.width, linePixel);
6564 if (e.ranges.length > 0) {
6565 e.start = e.ranges[0];
6566 e.end = e.ranges[e.ranges.length - 1];
6567 }
6568 } else {
6569 if (e.index == 0) {
6570 Point point = st.getSelection();
6571 e.start = point.x;
6572 e.end = point.y;
6573 if (e.start > e.end) {
6574 int temp = e.start;
6575 e.start = e.end;
6576 e.end = temp;
6577 }
6578 }
6579 }
6580 }
6581 @Override
6582 public void getSelectionCount(AccessibleTextEvent e) {
6583 StyledText st = StyledText.this;
6584 if (st.blockSelection && st.blockXLocation != -1) {
6585 Rectangle rect = st.getBlockSelectionPosition();
6586 e.count = rect.height - rect.y + 1;
6587 } else {
6588 Point point = st.getSelection();
6589 e.count = point.x == point.y ? 0 : 1;
6590 }
6591 }
6592 @Override
6593 public void removeSelection(AccessibleTextEvent e) {
6594 StyledText st = StyledText.this;
6595 if (e.index == 0) {
6596 if (st.blockSelection) {
6597 st.clearBlockSelection(true, false);
6598 } else {
6599 st.clearSelection(false);
6600 }
6601 e.result = ACC.OK;
6602 }
6603 }
6604 @Override
6605 public void setSelection(AccessibleTextEvent e) {
6606 if (e.index != 0) return;
6607 StyledText st = StyledText.this;
6608 Point point = st.getSelection();
6609 if (point.x == point.y) return;
6610 int end = e.end;
6611 if (end == -1) end = st.getCharCount();
6612 st.setSelection(e.start, end);
6613 e.result = ACC.OK;
6614 }
6615 @Override
6616 public void getCharacterCount(AccessibleTextEvent e) {
6617 e.count = StyledText.this.getCharCount();
6618 }
6619 @Override
6620 public void getOffsetAtPoint(AccessibleTextEvent e) {
6621 StyledText st = StyledText.this;
6622 Point point = new Point (e.x, e.y);
6623 Display display = st.getDisplay();
6624 point = display.map(null, st, point);
6625 e.offset = st.getOffsetAtPoint(point.x, point.y, null, true);
6626 }
6627 @Override
6628 public void getTextBounds(AccessibleTextEvent e) {
6629 StyledText st = StyledText.this;
6630 int start = e.start;
6631 int end = e.end;
6632 int contentLength = st.getCharCount();
6633 start = Math.max(0, Math.min(start, contentLength));
6634 end = Math.max(0, Math.min(end, contentLength));
6635 if (start > end) {
6636 int temp = start;
6637 start = end;
6638 end = temp;
6639 }
6640 int startLine = st.getLineAtOffset(start);
6641 int endLine = st.getLineAtOffset(end);
6642 Rectangle[] rects = new Rectangle[endLine - startLine + 1];
6643 Rectangle bounds = null;
6644 int index = 0;
6645 Display display = st.getDisplay();
6646 for (int lineIndex = startLine; lineIndex <= endLine; lineIndex++) {
6647 Rectangle rect = new Rectangle(0, 0, 0, 0);
6648 rect.y = st.getLinePixel(lineIndex);
6649 rect.height = st.renderer.getLineHeight(lineIndex);
6650 if (lineIndex == startLine) {
6651 rect.x = st.getPointAtOffset(start).x;
6652 } else {
6653 rect.x = st.leftMargin - st.horizontalScrollOffset;
6654 }
6655 if (lineIndex == endLine) {
6656 rect.width = st.getPointAtOffset(end).x - rect.x;
6657 } else {
6658 TextLayout layout = st.renderer.getTextLayout(lineIndex);
6659 rect.width = layout.getBounds().width - rect.x;
6660 st.renderer.disposeTextLayout(layout);
6661 }
6662 rects [index++] = rect = display.map(st, null, rect);
6663 if (bounds == null) {
6664 bounds = new Rectangle(rect.x, rect.y, rect.width, rect.height);
6665 } else {
6666 bounds.add(rect);
6667 }
6668 }
6669 e.rectangles = rects;
6670 if (bounds != null) {
6671 e.x = bounds.x;
6672 e.y = bounds.y;
6673 e.width = bounds.width;
6674 e.height = bounds.height;
6675 }
6676 }
6677 int[] getRanges(int left, int top, int right, int bottom) {
6678 StyledText st = StyledText.this;
6679 int lineStart = st.getLineIndex(top);
6680 int lineEnd = st.getLineIndex(bottom);
6681 int count = lineEnd - lineStart + 1;
6682 int[] ranges = new int [count * 2];
6683 int index = 0;
6684 for (int lineIndex = lineStart; lineIndex <= lineEnd; lineIndex++) {
6685 String line = st.content.getLine(lineIndex);
6686 int lineOffset = st.content.getOffsetAtLine(lineIndex);
6687 int lineEndOffset = lineOffset + line.length();
6688 int linePixel = st.getLinePixel(lineIndex);
6689 int start = st.getOffsetAtPoint(left, linePixel, null, true);
6690 if (start == -1) {
6691 start = left < st.leftMargin ? lineOffset : lineEndOffset;
6692 }
6693 int[] trailing = new int[1];
6694 int end = st.getOffsetAtPoint(right, linePixel, trailing, true);
6695 if (end == -1) {
6696 end = right < st.leftMargin ? lineOffset : lineEndOffset;
6697 } else {
6698 end += trailing[0];
6699 }
6700 if (start > end) {
6701 int temp = start;
6702 start = end;
6703 end = temp;
6704 }
6705 ranges[index++] = start;
6706 ranges[index++] = end;
6707 }
6708 return ranges;
6709 }
6710 @Override
6711 public void getRanges(AccessibleTextEvent e) {
6712 StyledText st = StyledText.this;
6713 Point point = new Point (e.x, e.y);
6714 Display display = st.getDisplay();
6715 point = display.map(null, st, point);
6716 e.ranges = getRanges(point.x, point.y, point.x + e.width, point.y + e.height);
6717 if (e.ranges.length > 0) {
6718 e.start = e.ranges[0];
6719 e.end = e.ranges[e.ranges.length - 1];
6720 }
6721 }
6722 @Override
6723 public void getText(AccessibleTextEvent e) {
6724 StyledText st = StyledText.this;
6725 int start = e.start;
6726 int end = e.end;
6727 int contentLength = st.getCharCount();
6728 if (end == -1) end = contentLength;
6729 start = Math.max(0, Math.min(start, contentLength));
6730 end = Math.max(0, Math.min(end, contentLength));
6731 if (start > end) {
6732 int temp = start;
6733 start = end;
6734 end = temp;
6735 }
6736 int count = e.count;
6737 switch (e.type) {
6738 case ACC.TEXT_BOUNDARY_ALL:
6739 //nothing to do
6740 break;
6741 case ACC.TEXT_BOUNDARY_CHAR: {
6742 int newCount = 0;
6743 if (count > 0) {
6744 while (count-- > 0) {
6745 int newEnd = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
6746 if (newEnd == contentLength) break;
6747 if (newEnd == end) break;
6748 end = newEnd;
6749 newCount++;
6750 }
6751 start = end;
6752 end = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
6753 } else {
6754 while (count++ < 0) {
6755 int newStart = st.getWordPrevious(start, SWT.MOVEMENT_CLUSTER);
6756 if (newStart == start) break;
6757 start = newStart;
6758 newCount--;
6759 }
6760 end = st.getWordNext(start, SWT.MOVEMENT_CLUSTER);
6761 }
6762 count = newCount;
6763 break;
6764 }
6765 case ACC.TEXT_BOUNDARY_WORD: {
6766 int newCount = 0;
6767 if (count > 0) {
6768 while (count-- > 0) {
6769 int newEnd = st.getWordNext(end, SWT.MOVEMENT_WORD_START, true);
6770 if (newEnd == end) break;
6771 newCount++;
6772 end = newEnd;
6773 }
6774 start = end;
6775 end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
6776 } else {
6777 if (st.getWordPrevious(Math.min(start + 1, contentLength), SWT.MOVEMENT_WORD_START, true) == start) {
6778 //start is a word start already
6779 count++;
6780 }
6781 while (count <= 0) {
6782 int newStart = st.getWordPrevious(start, SWT.MOVEMENT_WORD_START, true);
6783 if (newStart == start) break;
6784 count++;
6785 start = newStart;
6786 if (count != 0) newCount--;
6787 }
6788 if (count <= 0 && start == 0) {
6789 end = start;
6790 } else {
6791 end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
6792 }
6793 }
6794 count = newCount;
6795 break;
6796 }
6797 case ACC.TEXT_BOUNDARY_LINE:
6798 //TODO implement line
6799 case ACC.TEXT_BOUNDARY_PARAGRAPH:
6800 case ACC.TEXT_BOUNDARY_SENTENCE: {
6801 int offset = count > 0 ? end : start;
6802 int lineIndex = st.getLineAtOffset(offset) + count;
6803 lineIndex = Math.max(0, Math.min(lineIndex, st.getLineCount() - 1));
6804 start = st.getOffsetAtLine(lineIndex);
6805 String line = st.getLine(lineIndex);
6806 end = start + line.length();
6807 count = lineIndex - st.getLineAtOffset(offset);
6808 break;
6809 }
6810 }
6811 e.start = start;
6812 e.end = end;
6813 e.count = count;
6814 e.result = st.content.getTextRange(start, end - start);
6815 }
6816 @Override
6817 public void getVisibleRanges(AccessibleTextEvent e) {
6818 e.ranges = getRanges(leftMargin, topMargin, clientAreaWidth - rightMargin, clientAreaHeight - bottomMargin);
6819 if (e.ranges.length > 0) {
6820 e.start = e.ranges[0];
6821 e.end = e.ranges[e.ranges.length - 1];
6822 }
6823 }
6824 @Override
6825 public void scrollText(AccessibleTextEvent e) {
6826 StyledText st = StyledText.this;
6827 int topPixel = getTopPixel(), horizontalPixel = st.getHorizontalPixel();
6828 switch (e.type) {
6829 case ACC.SCROLL_TYPE_ANYWHERE:
6830 case ACC.SCROLL_TYPE_TOP_LEFT:
6831 case ACC.SCROLL_TYPE_LEFT_EDGE:
6832 case ACC.SCROLL_TYPE_TOP_EDGE: {
6833 Rectangle rect = st.getBoundsAtOffset(e.start);
6834 if (e.type != ACC.SCROLL_TYPE_TOP_EDGE) {
6835 horizontalPixel = horizontalPixel + rect.x - st.leftMargin;
6836 }
6837 if (e.type != ACC.SCROLL_TYPE_LEFT_EDGE) {
6838 topPixel = topPixel + rect.y - st.topMargin;
6839 }
6840 break;
6841 }
6842 case ACC.SCROLL_TYPE_BOTTOM_RIGHT:
6843 case ACC.SCROLL_TYPE_BOTTOM_EDGE:
6844 case ACC.SCROLL_TYPE_RIGHT_EDGE: {
6845 Rectangle rect = st.getBoundsAtOffset(e.end - 1);
6846 if (e.type != ACC.SCROLL_TYPE_BOTTOM_EDGE) {
6847 horizontalPixel = horizontalPixel - st.clientAreaWidth + rect.x + rect.width + st.rightMargin;
6848 }
6849 if (e.type != ACC.SCROLL_TYPE_RIGHT_EDGE) {
6850 topPixel = topPixel - st.clientAreaHeight + rect.y +rect.height + st.bottomMargin;
6851 }
6852 break;
6853 }
6854 case ACC.SCROLL_TYPE_POINT: {
6855 Point point = new Point(e.x, e.y);
6856 Display display = st.getDisplay();
6857 point = display.map(null, st, point);
6858 Rectangle rect = st.getBoundsAtOffset(e.start);
6859 topPixel = topPixel - point.y + rect.y;
6860 horizontalPixel = horizontalPixel - point.x + rect.x;
6861 break;
6862 }
6863 }
6864 st.setTopPixel(topPixel);
6865 st.setHorizontalPixel(horizontalPixel);
6866 e.result = ACC.OK;
6867 }
6868 };
6869 acc.addAccessibleTextListener(accTextExtendedAdapter);
6870
6871 accEditableTextListener = new AccessibleEditableTextListener() {
6872 @Override
6873 public void setTextAttributes(AccessibleTextAttributeEvent e) {
6874 // This method must be implemented by the application
6875 e.result = ACC.OK;
6876 }
6877 @Override
6878 public void replaceText(AccessibleEditableTextEvent e) {
6879 StyledText st = StyledText.this;
6880 st.replaceTextRange(e.start, e.end - e.start, e.string);
6881 e.result = ACC.OK;
6882 }
6883 @Override
6884 public void pasteText(AccessibleEditableTextEvent e) {
6885 StyledText st = StyledText.this;
6886 st.setSelection(e.start);
6887 st.paste();
6888 e.result = ACC.OK;
6889 }
6890 @Override
6891 public void cutText(AccessibleEditableTextEvent e) {
6892 StyledText st = StyledText.this;
6893 st.setSelection(e.start, e.end);
6894 st.cut();
6895 e.result = ACC.OK;
6896 }
6897 @Override
6898 public void copyText(AccessibleEditableTextEvent e) {
6899 StyledText st = StyledText.this;
6900 st.setSelection(e.start, e.end);
6901 st.copy();
6902 e.result = ACC.OK;
6903 }
6904 };
6905 acc.addAccessibleEditableTextListener(accEditableTextListener);
6906
6907 accAttributeAdapter = new AccessibleAttributeAdapter() {
6908 @Override
6909 public void getAttributes(AccessibleAttributeEvent e) {
6910 StyledText st = StyledText.this;
6911 e.leftMargin = st.getLeftMargin();
6912 e.topMargin = st.getTopMargin();
6913 e.rightMargin = st.getRightMargin();
6914 e.bottomMargin = st.getBottomMargin();
6915 e.tabStops = st.getTabStops();
6916 e.justify = st.getJustify();
6917 e.alignment = st.getAlignment();
6918 e.indent = st.getIndent();
6919 }
6920 @Override
6921 public void getTextAttributes(AccessibleTextAttributeEvent e) {
6922 StyledText st = StyledText.this;
6923 int contentLength = st.getCharCount();
6924 if (!isListening(ST.LineGetStyle) && st.renderer.styleCount == 0) {
6925 e.start = 0;
6926 e.end = contentLength;
6927 e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
6928 return;
6929 }
6930 int offset = Math.max(0, Math.min(e.offset, contentLength - 1));
6931 int lineIndex = st.getLineAtOffset(offset);
6932 int lineOffset = st.getOffsetAtLine(lineIndex);
6933 int lineCount = st.getLineCount();
6934 offset = offset - lineOffset;
6935
6936 TextLayout layout = st.renderer.getTextLayout(lineIndex);
6937 int lineLength = layout.getText().length();
6938 if (lineLength > 0) {
6939 e.textStyle = layout.getStyle(Math.max(0, Math.min(offset, lineLength - 1)));
6940 }
6941
6942 // If no override info available, use defaults. Don't supply default colors, though.
6943 if (e.textStyle == null) {
6944 e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
6945 } else {
6946 if (e.textStyle.foreground == null || e.textStyle.background == null || e.textStyle.font == null) {
6947 TextStyle textStyle = new TextStyle(e.textStyle);
6948 if (textStyle.foreground == null) textStyle.foreground = st.foreground;
6949 if (textStyle.background == null) textStyle.background = st.background;
6950 if (textStyle.font == null) textStyle.font = st.getFont();
6951 e.textStyle = textStyle;
6952 }
6953 }
6954
6955 //offset at line delimiter case
6956 if (offset >= lineLength) {
6957 e.start = lineOffset + lineLength;
6958 if (lineIndex + 1 < lineCount) {
6959 e.end = st.getOffsetAtLine(lineIndex + 1);
6960 } else {
6961 e.end = contentLength;
6962 }
6963 return;
6964 }
6965
6966 int[] ranges = layout.getRanges();
6967 st.renderer.disposeTextLayout(layout);
6968 int index = 0;
6969 int end = 0;
6970 while (index < ranges.length) {
6971 int styleStart = ranges[index++];
6972 int styleEnd = ranges[index++];
6973 if (styleStart <= offset && offset <= styleEnd) {
6974 e.start = lineOffset + styleStart;
6975 e.end = lineOffset + styleEnd + 1;
6976 return;
6977 }
6978 if (styleStart > offset) {
6979 e.start = lineOffset + end;
6980 e.end = lineOffset + styleStart;
6981 return;
6982 }
6983 end = styleEnd + 1;
6984 }
6985 if (index == ranges.length) {
6986 e.start = lineOffset + end;
6987 if (lineIndex + 1 < lineCount) {
6988 e.end = st.getOffsetAtLine(lineIndex + 1);
6989 } else {
6990 e.end = contentLength;
6991 }
6992 }
6993 }
6994 };
6995 acc.addAccessibleAttributeListener(accAttributeAdapter);
6996
6997 accControlAdapter = new AccessibleControlAdapter() {
6998 @Override
6999 public void getRole(AccessibleControlEvent e) {
7000 e.detail = ACC.ROLE_TEXT;
7001 }
7002 @Override
7003 public void getState(AccessibleControlEvent e) {
7004 int state = 0;
7005 if (isEnabled()) state |= ACC.STATE_FOCUSABLE;
7006 if (isFocusControl()) state |= ACC.STATE_FOCUSED;
7007 if (!isVisible()) state |= ACC.STATE_INVISIBLE;
7008 if (!getEditable()) state |= ACC.STATE_READONLY;
7009 if (isSingleLine()) state |= ACC.STATE_SINGLELINE;
7010 else state |= ACC.STATE_MULTILINE;
7011 e.detail = state;
7012 }
7013 @Override
7014 public void getValue(AccessibleControlEvent e) {
7015 e.result = StyledText.this.getText();
7016 }
7017 };
7018 acc.addAccessibleControlListener(accControlAdapter);
7019
7020 addListener(SWT.FocusIn, event -> acc.setFocus(ACC.CHILDID_SELF));
7021 }
7022
7023 @Override
dispose()7024 public void dispose() {
7025 /*
7026 * Note: It is valid to attempt to dispose a widget more than once.
7027 * Added check for this.
7028 */
7029 if (!isDisposed()) {
7030 acc.removeAccessibleControlListener(accControlAdapter);
7031 acc.removeAccessibleAttributeListener(accAttributeAdapter);
7032 acc.removeAccessibleEditableTextListener(accEditableTextListener);
7033 acc.removeAccessibleTextListener(accTextExtendedAdapter);
7034 acc.removeAccessibleListener(accAdapter);
7035 }
7036 super.dispose();
7037 }
7038
7039 /*
7040 * Return the Label immediately preceding the receiver in the z-order,
7041 * or null if none.
7042 */
getAssociatedLabel()7043 String getAssociatedLabel () {
7044 Control[] siblings = getParent ().getChildren ();
7045 for (int i = 0; i < siblings.length; i++) {
7046 if (siblings [i] == StyledText.this) {
7047 if (i > 0) {
7048 Control sibling = siblings [i-1];
7049 if (sibling instanceof Label) return ((Label) sibling).getText();
7050 if (sibling instanceof CLabel) return ((CLabel) sibling).getText();
7051 }
7052 break;
7053 }
7054 }
7055 return null;
7056 }
stripMnemonic(String string)7057 String stripMnemonic (String string) {
7058 int index = 0;
7059 int length = string.length ();
7060 do {
7061 while ((index < length) && (string.charAt (index) != '&')) index++;
7062 if (++index >= length) return string;
7063 if (string.charAt (index) != '&') {
7064 return string.substring(0, index-1) + string.substring(index, length);
7065 }
7066 index++;
7067 } while (index < length);
7068 return string;
7069 }
7070 /*
7071 * Return the lowercase of the first non-'&' character following
7072 * an '&' character in the given string. If there are no '&'
7073 * characters in the given string, return '\0'.
7074 */
_findMnemonic(String string)7075 char _findMnemonic (String string) {
7076 if (string == null) return '\0';
7077 int index = 0;
7078 int length = string.length ();
7079 do {
7080 while (index < length && string.charAt (index) != '&') index++;
7081 if (++index >= length) return '\0';
7082 if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index));
7083 index++;
7084 } while (index < length);
7085 return '\0';
7086 }
7087 /**
7088 * Executes the action.
7089 *
7090 * @param action one of the actions defined in ST.java
7091 */
invokeAction(int action)7092 public void invokeAction(int action) {
7093 checkWidget();
7094 if (blockSelection && invokeBlockAction(action)) return;
7095 updateCaretDirection = true;
7096 switch (action) {
7097 // Navigation
7098 case ST.LINE_UP:
7099 doLineUp(false);
7100 clearSelection(true);
7101 break;
7102 case ST.LINE_DOWN:
7103 doLineDown(false);
7104 clearSelection(true);
7105 break;
7106 case ST.LINE_START:
7107 doLineStart();
7108 clearSelection(true);
7109 break;
7110 case ST.LINE_END:
7111 doLineEnd();
7112 clearSelection(true);
7113 break;
7114 case ST.COLUMN_PREVIOUS:
7115 doCursorPrevious();
7116 clearSelection(true);
7117 break;
7118 case ST.COLUMN_NEXT:
7119 doCursorNext();
7120 clearSelection(true);
7121 break;
7122 case ST.PAGE_UP:
7123 doPageUp(false, -1);
7124 clearSelection(true);
7125 break;
7126 case ST.PAGE_DOWN:
7127 doPageDown(false, -1);
7128 clearSelection(true);
7129 break;
7130 case ST.WORD_PREVIOUS:
7131 doWordPrevious();
7132 clearSelection(true);
7133 break;
7134 case ST.WORD_NEXT:
7135 doWordNext();
7136 clearSelection(true);
7137 break;
7138 case ST.TEXT_START:
7139 doContentStart();
7140 clearSelection(true);
7141 break;
7142 case ST.TEXT_END:
7143 doContentEnd();
7144 clearSelection(true);
7145 break;
7146 case ST.WINDOW_START:
7147 doPageStart();
7148 clearSelection(true);
7149 break;
7150 case ST.WINDOW_END:
7151 doPageEnd();
7152 clearSelection(true);
7153 break;
7154 // Selection
7155 case ST.SELECT_LINE_UP:
7156 doSelectionLineUp();
7157 break;
7158 case ST.SELECT_ALL:
7159 selectAll();
7160 break;
7161 case ST.SELECT_LINE_DOWN:
7162 doSelectionLineDown();
7163 break;
7164 case ST.SELECT_LINE_START:
7165 doLineStart();
7166 doSelection(ST.COLUMN_PREVIOUS);
7167 break;
7168 case ST.SELECT_LINE_END:
7169 doLineEnd();
7170 doSelection(ST.COLUMN_NEXT);
7171 break;
7172 case ST.SELECT_COLUMN_PREVIOUS:
7173 doSelectionCursorPrevious();
7174 doSelection(ST.COLUMN_PREVIOUS);
7175 break;
7176 case ST.SELECT_COLUMN_NEXT:
7177 doSelectionCursorNext();
7178 doSelection(ST.COLUMN_NEXT);
7179 break;
7180 case ST.SELECT_PAGE_UP:
7181 doSelectionPageUp(-1);
7182 break;
7183 case ST.SELECT_PAGE_DOWN:
7184 doSelectionPageDown(-1);
7185 break;
7186 case ST.SELECT_WORD_PREVIOUS:
7187 doSelectionWordPrevious();
7188 doSelection(ST.COLUMN_PREVIOUS);
7189 break;
7190 case ST.SELECT_WORD_NEXT:
7191 doSelectionWordNext();
7192 doSelection(ST.COLUMN_NEXT);
7193 break;
7194 case ST.SELECT_TEXT_START:
7195 doContentStart();
7196 doSelection(ST.COLUMN_PREVIOUS);
7197 break;
7198 case ST.SELECT_TEXT_END:
7199 doContentEnd();
7200 doSelection(ST.COLUMN_NEXT);
7201 break;
7202 case ST.SELECT_WINDOW_START:
7203 doPageStart();
7204 doSelection(ST.COLUMN_PREVIOUS);
7205 break;
7206 case ST.SELECT_WINDOW_END:
7207 doPageEnd();
7208 doSelection(ST.COLUMN_NEXT);
7209 break;
7210 // Modification
7211 case ST.CUT:
7212 cut();
7213 break;
7214 case ST.COPY:
7215 copy();
7216 break;
7217 case ST.PASTE:
7218 paste();
7219 break;
7220 case ST.DELETE_PREVIOUS:
7221 doBackspace();
7222 break;
7223 case ST.DELETE_NEXT:
7224 doDelete();
7225 break;
7226 case ST.DELETE_WORD_PREVIOUS:
7227 doDeleteWordPrevious();
7228 break;
7229 case ST.DELETE_WORD_NEXT:
7230 doDeleteWordNext();
7231 break;
7232 // Miscellaneous
7233 case ST.TOGGLE_OVERWRITE:
7234 overwrite = !overwrite; // toggle insert/overwrite mode
7235 break;
7236 case ST.TOGGLE_BLOCKSELECTION:
7237 setBlockSelection(!blockSelection);
7238 break;
7239 }
7240 }
7241 /**
7242 * Returns true if an action should not be performed when block
7243 * selection in active
7244 */
invokeBlockAction(int action)7245 boolean invokeBlockAction(int action) {
7246 switch (action) {
7247 // Navigation
7248 case ST.LINE_UP:
7249 case ST.LINE_DOWN:
7250 case ST.LINE_START:
7251 case ST.LINE_END:
7252 case ST.COLUMN_PREVIOUS:
7253 case ST.COLUMN_NEXT:
7254 case ST.PAGE_UP:
7255 case ST.PAGE_DOWN:
7256 case ST.WORD_PREVIOUS:
7257 case ST.WORD_NEXT:
7258 case ST.TEXT_START:
7259 case ST.TEXT_END:
7260 case ST.WINDOW_START:
7261 case ST.WINDOW_END:
7262 clearBlockSelection(false, false);
7263 return false;
7264 // Selection
7265 case ST.SELECT_LINE_UP:
7266 doBlockLineVertical(true);
7267 return true;
7268 case ST.SELECT_LINE_DOWN:
7269 doBlockLineVertical(false);
7270 return true;
7271 case ST.SELECT_LINE_START:
7272 doBlockLineHorizontal(false);
7273 return true;
7274 case ST.SELECT_LINE_END:
7275 doBlockLineHorizontal(true);
7276 return false;
7277 case ST.SELECT_COLUMN_PREVIOUS:
7278 doBlockColumn(false);
7279 return true;
7280 case ST.SELECT_COLUMN_NEXT:
7281 doBlockColumn(true);
7282 return true;
7283 case ST.SELECT_WORD_PREVIOUS:
7284 doBlockWord(false);
7285 return true;
7286 case ST.SELECT_WORD_NEXT:
7287 doBlockWord(true);
7288 return true;
7289 case ST.SELECT_ALL:
7290 return false;
7291 case ST.SELECT_TEXT_START:
7292 doBlockContentStartEnd(false);
7293 break;
7294 case ST.SELECT_TEXT_END:
7295 doBlockContentStartEnd(true);
7296 break;
7297 case ST.SELECT_PAGE_UP:
7298 case ST.SELECT_PAGE_DOWN:
7299 case ST.SELECT_WINDOW_START:
7300 case ST.SELECT_WINDOW_END:
7301 //blocked actions
7302 return true;
7303 // Modification
7304 case ST.CUT:
7305 case ST.COPY:
7306 case ST.PASTE:
7307 return false;
7308 case ST.DELETE_PREVIOUS:
7309 case ST.DELETE_NEXT:
7310 if (blockXLocation != -1) {
7311 insertBlockSelectionText((char)0, action);
7312 return true;
7313 }
7314 return false;
7315 case ST.DELETE_WORD_PREVIOUS:
7316 case ST.DELETE_WORD_NEXT:
7317 //blocked actions
7318 return blockXLocation != -1;
7319 }
7320 return false;
7321 }
isBidiCaret()7322 boolean isBidiCaret() {
7323 return BidiUtil.isBidiPlatform();
7324 }
isFixedLineHeight()7325 boolean isFixedLineHeight() {
7326 return !isWordWrap() && lineSpacing == 0 && renderer.lineSpacingProvider == null && !hasStyleWithVariableHeight && !hasVerticalIndent;
7327 }
7328 /**
7329 * Returns whether the given offset is inside a multi byte line delimiter.
7330 * Example:
7331 * "Line1\r\n" isLineDelimiter(5) == false but isLineDelimiter(6) == true
7332 *
7333 * @return true if the given offset is inside a multi byte line delimiter.
7334 * false if the given offset is before or after a line delimiter.
7335 */
isLineDelimiter(int offset)7336 boolean isLineDelimiter(int offset) {
7337 int line = content.getLineAtOffset(offset);
7338 int lineOffset = content.getOffsetAtLine(line);
7339 int offsetInLine = offset - lineOffset;
7340 // offsetInLine will be greater than line length if the line
7341 // delimiter is longer than one character and the offset is set
7342 // in between parts of the line delimiter.
7343 return offsetInLine > content.getLine(line).length();
7344 }
7345 /**
7346 * Returns whether the widget is mirrored (right oriented/right to left
7347 * writing order).
7348 *
7349 * @return isMirrored true=the widget is right oriented, false=the widget
7350 * is left oriented
7351 */
isMirrored()7352 boolean isMirrored() {
7353 return (getStyle() & SWT.MIRRORED) != 0;
7354 }
7355 /**
7356 * Returns <code>true</code> if any text in the widget is selected,
7357 * and <code>false</code> otherwise.
7358 *
7359 * @return the text selection state
7360 * @exception SWTException <ul>
7361 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7362 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7363 * </ul>
7364 *
7365 * @since 3.103
7366 */
isTextSelected()7367 public boolean isTextSelected() {
7368 checkWidget();
7369 if (blockSelection && blockXLocation != -1) {
7370 Rectangle rect = getBlockSelectionPosition();
7371 return !rect.isEmpty();
7372 }
7373 return selection.y != selection.x;
7374 }
7375 /**
7376 * Returns whether the widget can have only one line.
7377 *
7378 * @return true if widget can have only one line, false if widget can have
7379 * multiple lines
7380 */
isSingleLine()7381 boolean isSingleLine() {
7382 return (getStyle() & SWT.SINGLE) != 0;
7383 }
7384
7385 /**
7386 * Sends the specified verify event, replace/insert text as defined by
7387 * the event and send a modify event.
7388 *
7389 * @param event the text change event.
7390 * <ul>
7391 * <li>event.start - the replace start offset</li>
7392 * <li>event.end - the replace end offset</li>
7393 * <li>event.text - the new text</li>
7394 * </ul>
7395 * @param updateCaret whether or not he caret should be set behind
7396 * the new text
7397 */
modifyContent(Event event, boolean updateCaret)7398 void modifyContent(Event event, boolean updateCaret) {
7399 event.doit = true;
7400 notifyListeners(SWT.Verify, event);
7401 if (event.doit) {
7402 StyledTextEvent styledTextEvent = null;
7403 int replacedLength = event.end - event.start;
7404 if (isListening(ST.ExtendedModify)) {
7405 styledTextEvent = new StyledTextEvent(content);
7406 styledTextEvent.start = event.start;
7407 styledTextEvent.end = event.start + event.text.length();
7408 styledTextEvent.text = content.getTextRange(event.start, replacedLength);
7409 }
7410 if (updateCaret) {
7411 //Fix advancing flag for delete/backspace key on direction boundary
7412 if (event.text.length() == 0) {
7413 int lineIndex = content.getLineAtOffset(event.start);
7414 int lineOffset = content.getOffsetAtLine(lineIndex);
7415 TextLayout layout = renderer.getTextLayout(lineIndex);
7416 int levelStart = layout.getLevel(event.start - lineOffset);
7417 int lineIndexEnd = content.getLineAtOffset(event.end);
7418 if (lineIndex != lineIndexEnd) {
7419 renderer.disposeTextLayout(layout);
7420 lineOffset = content.getOffsetAtLine(lineIndexEnd);
7421 layout = renderer.getTextLayout(lineIndexEnd);
7422 }
7423 int levelEnd = layout.getLevel(event.end - lineOffset);
7424 renderer.disposeTextLayout(layout);
7425 if (levelStart != levelEnd) {
7426 caretAlignment = PREVIOUS_OFFSET_TRAILING;
7427 } else {
7428 caretAlignment = OFFSET_LEADING;
7429 }
7430 }
7431 }
7432 content.replaceTextRange(event.start, replacedLength, event.text);
7433 // set the caret position prior to sending the modify event.
7434 // fixes 1GBB8NJ
7435 if (updateCaret && !(blockSelection && blockXLocation != -1)) {
7436 // always update the caret location. fixes 1G8FODP
7437 setSelection(event.start + event.text.length(), 0, true, false);
7438 showCaret();
7439 }
7440 notifyListeners(SWT.Modify, event);
7441 if (isListening(ST.ExtendedModify)) {
7442 notifyListeners(ST.ExtendedModify, styledTextEvent);
7443 }
7444 }
7445 }
paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex)7446 void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) {
7447 if (isListening(ST.PaintObject)) {
7448 StyledTextEvent event = new StyledTextEvent (content) ;
7449 event.gc = gc;
7450 event.x = x;
7451 event.y = y;
7452 event.ascent = ascent;
7453 event.descent = descent;
7454 event.style = style;
7455 event.bullet = bullet;
7456 event.bulletIndex = bulletIndex;
7457 notifyListeners(ST.PaintObject, event);
7458 }
7459 }
7460 /**
7461 * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
7462 * clipboard or, if there is no selection, inserts the text at the current
7463 * caret offset. If the widget has the SWT.SINGLE style and the
7464 * clipboard text contains more than one line, only the first line without
7465 * line delimiters is inserted in the widget.
7466 *
7467 * @exception SWTException <ul>
7468 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7469 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7470 * </ul>
7471 */
paste()7472 public void paste(){
7473 checkWidget();
7474 String text = (String) getClipboardContent(DND.CLIPBOARD);
7475 if (text != null && text.length() > 0) {
7476 if (blockSelection) {
7477 boolean fillWithSpaces = isFixedLineHeight() && renderer.fixedPitch;
7478 int offset = insertBlockSelectionText(text, fillWithSpaces);
7479 setCaretOffset(offset, SWT.DEFAULT);
7480 clearBlockSelection(true, true);
7481 setCaretLocation();
7482 return;
7483 }
7484 Event event = new Event();
7485 event.start = selection.x;
7486 event.end = selection.y;
7487 String delimitedText = getModelDelimitedText(text);
7488 if (textLimit > 0) {
7489 int uneditedTextLength = getCharCount() - (selection.y - selection.x);
7490 if ((uneditedTextLength + delimitedText.length()) > textLimit) {
7491 int endIndex = textLimit - uneditedTextLength;
7492 delimitedText = delimitedText.substring(0, Math.max(endIndex, 0));
7493 }
7494 }
7495 event.text = delimitedText;
7496 sendKeyEvent(event);
7497 }
7498 }
pasteOnMiddleClick(Event event)7499 private void pasteOnMiddleClick(Event event) {
7500 String text = (String)getClipboardContent(DND.SELECTION_CLIPBOARD);
7501 if (text != null && text.length() > 0) {
7502 // position cursor
7503 doMouseLocationChange(event.x, event.y, false);
7504 // insert text
7505 Event e = new Event();
7506 e.start = selection.x;
7507 e.end = selection.y;
7508 e.text = getModelDelimitedText(text);
7509 sendKeyEvent(e);
7510 }
7511 }
7512 /**
7513 * Prints the widget's text to the default printer.
7514 *
7515 * @exception SWTException <ul>
7516 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7517 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7518 * </ul>
7519 */
print()7520 public void print() {
7521 checkWidget();
7522 Printer printer = new Printer();
7523 StyledTextPrintOptions options = new StyledTextPrintOptions();
7524 options.printTextForeground = true;
7525 options.printTextBackground = true;
7526 options.printTextFontStyle = true;
7527 options.printLineBackground = true;
7528 new Printing(this, printer, options).run();
7529 printer.dispose();
7530 }
7531 /**
7532 * Returns a runnable that will print the widget's text
7533 * to the specified printer.
7534 * <p>
7535 * The runnable may be run in a non-UI thread.
7536 * </p>
7537 *
7538 * @param printer the printer to print to
7539 *
7540 * @return a <code>Runnable</code> for printing the receiver's text
7541 *
7542 * @exception SWTException <ul>
7543 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7544 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7545 * </ul>
7546 * @exception IllegalArgumentException <ul>
7547 * <li>ERROR_NULL_ARGUMENT when printer is null</li>
7548 * </ul>
7549 */
print(Printer printer)7550 public Runnable print(Printer printer) {
7551 checkWidget();
7552 if (printer == null) {
7553 SWT.error(SWT.ERROR_NULL_ARGUMENT);
7554 }
7555 StyledTextPrintOptions options = new StyledTextPrintOptions();
7556 options.printTextForeground = true;
7557 options.printTextBackground = true;
7558 options.printTextFontStyle = true;
7559 options.printLineBackground = true;
7560 return print(printer, options);
7561 }
7562 /**
7563 * Returns a runnable that will print the widget's text
7564 * to the specified printer.
7565 * <p>
7566 * The runnable may be run in a non-UI thread.
7567 * </p>
7568 *
7569 * @param printer the printer to print to
7570 * @param options print options to use during printing
7571 *
7572 * @return a <code>Runnable</code> for printing the receiver's text
7573 *
7574 * @exception SWTException <ul>
7575 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7576 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7577 * </ul>
7578 * @exception IllegalArgumentException <ul>
7579 * <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
7580 * </ul>
7581 * @since 2.1
7582 */
print(Printer printer, StyledTextPrintOptions options)7583 public Runnable print(Printer printer, StyledTextPrintOptions options) {
7584 checkWidget();
7585 if (printer == null || options == null) {
7586 SWT.error(SWT.ERROR_NULL_ARGUMENT);
7587 }
7588 return new Printing(this, printer, options);
7589 }
7590 /**
7591 * Causes the entire bounds of the receiver to be marked
7592 * as needing to be redrawn. The next time a paint request
7593 * is processed, the control will be completely painted.
7594 * <p>
7595 * Recalculates the content width for all lines in the bounds.
7596 * When a <code>LineStyleListener</code> is used a redraw call
7597 * is the only notification to the widget that styles have changed
7598 * and that the content width may have changed.
7599 * </p>
7600 *
7601 * @exception SWTException <ul>
7602 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7603 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7604 * </ul>
7605 *
7606 * @see Control#update()
7607 */
7608 @Override
redraw()7609 public void redraw() {
7610 super.redraw();
7611 int itemCount = getPartialBottomIndex() - topIndex + 1;
7612 renderer.reset(topIndex, itemCount);
7613 renderer.calculate(topIndex, itemCount);
7614 setScrollBars(false);
7615 doMouseLinkCursor();
7616 }
7617 /**
7618 * Causes the rectangular area of the receiver specified by
7619 * the arguments to be marked as needing to be redrawn.
7620 * The next time a paint request is processed, that area of
7621 * the receiver will be painted. If the <code>all</code> flag
7622 * is <code>true</code>, any children of the receiver which
7623 * intersect with the specified area will also paint their
7624 * intersecting areas. If the <code>all</code> flag is
7625 * <code>false</code>, the children will not be painted.
7626 * <p>
7627 * Marks the content width of all lines in the specified rectangle
7628 * as unknown. Recalculates the content width of all visible lines.
7629 * When a <code>LineStyleListener</code> is used a redraw call
7630 * is the only notification to the widget that styles have changed
7631 * and that the content width may have changed.
7632 * </p>
7633 *
7634 * @param x the x coordinate of the area to draw
7635 * @param y the y coordinate of the area to draw
7636 * @param width the width of the area to draw
7637 * @param height the height of the area to draw
7638 * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
7639 *
7640 * @exception SWTException <ul>
7641 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7642 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7643 * </ul>
7644 *
7645 * @see Control#update()
7646 */
7647 @Override
redraw(int x, int y, int width, int height, boolean all)7648 public void redraw(int x, int y, int width, int height, boolean all) {
7649 super.redraw(x, y, width, height, all);
7650 if (height > 0) {
7651 int firstLine = getLineIndex(y);
7652 int lastLine = getLineIndex(y + height);
7653 resetCache(firstLine, lastLine - firstLine + 1);
7654 doMouseLinkCursor();
7655 }
7656 }
redrawLines(int startLine, int lineCount, boolean bottomChanged)7657 void redrawLines(int startLine, int lineCount, boolean bottomChanged) {
7658 // do nothing if redraw range is completely invisible
7659 int endLine = startLine + lineCount - 1;
7660 int partialBottomIndex = getPartialBottomIndex();
7661 int partialTopIndex = getPartialTopIndex();
7662 if (startLine > partialBottomIndex || endLine < partialTopIndex) {
7663 return;
7664 }
7665 // only redraw visible lines
7666 if (startLine < partialTopIndex) {
7667 startLine = partialTopIndex;
7668 }
7669 if (endLine > partialBottomIndex) {
7670 endLine = partialBottomIndex;
7671 }
7672 int redrawTop = getLinePixel(startLine);
7673 int redrawBottom = getLinePixel(endLine + 1);
7674 if (bottomChanged) redrawBottom = clientAreaHeight - bottomMargin;
7675 int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
7676 super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
7677 }
redrawLinesBullet(int[] redrawLines)7678 void redrawLinesBullet (int[] redrawLines) {
7679 if (redrawLines == null) return;
7680 int topIndex = getPartialTopIndex();
7681 int bottomIndex = getPartialBottomIndex();
7682 for (int redrawLine : redrawLines) {
7683 int lineIndex = redrawLine;
7684 if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue;
7685 int width = -1;
7686 Bullet bullet = renderer.getLineBullet(lineIndex, null);
7687 if (bullet != null) {
7688 StyleRange style = bullet.style;
7689 GlyphMetrics metrics = style.metrics;
7690 width = metrics.width;
7691 }
7692 if (width == -1) width = getClientArea().width;
7693 int height = renderer.getLineHeight(lineIndex);
7694 int y = getLinePixel(lineIndex);
7695 super.redraw(0, y, width, height, false);
7696 }
7697 }
redrawMargins(int oldHeight, int oldWidth)7698 void redrawMargins(int oldHeight, int oldWidth) {
7699 /* Redraw the old or new right/bottom margin if needed */
7700 if (oldWidth != clientAreaWidth) {
7701 if (rightMargin > 0) {
7702 int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
7703 super.redraw(x, 0, rightMargin, oldHeight, false);
7704 }
7705 }
7706 if (oldHeight != clientAreaHeight) {
7707 if (bottomMargin > 0) {
7708 int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
7709 super.redraw(0, y, oldWidth, bottomMargin, false);
7710 }
7711 }
7712 }
7713 /**
7714 * Redraws the specified text range.
7715 *
7716 * @param start offset of the first character to redraw
7717 * @param length number of characters to redraw
7718 * @param clearBackground true if the background should be cleared as
7719 * part of the redraw operation. If true, the entire redraw range will
7720 * be cleared before anything is redrawn. If the redraw range includes
7721 * the last character of a line (i.e., the entire line is redrawn) the
7722 * line is cleared all the way to the right border of the widget.
7723 * The redraw operation will be faster and smoother if clearBackground
7724 * is set to false. Whether or not the flag can be set to false depends
7725 * on the type of change that has taken place. If font styles or
7726 * background colors for the redraw range have changed, clearBackground
7727 * should be set to true. If only foreground colors have changed for
7728 * the redraw range, clearBackground can be set to false.
7729 * @exception SWTException <ul>
7730 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7731 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7732 * </ul>
7733 * @exception IllegalArgumentException <ul>
7734 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
7735 * </ul>
7736 */
redrawRange(int start, int length, boolean clearBackground)7737 public void redrawRange(int start, int length, boolean clearBackground) {
7738 checkWidget();
7739 int end = start + length;
7740 int contentLength = content.getCharCount();
7741 if (start > end || start < 0 || end > contentLength) {
7742 SWT.error(SWT.ERROR_INVALID_RANGE);
7743 }
7744 int firstLine = content.getLineAtOffset(start);
7745 int lastLine = content.getLineAtOffset(end);
7746 resetCache(firstLine, lastLine - firstLine + 1);
7747 internalRedrawRange(start, length);
7748 doMouseLinkCursor();
7749 }
7750 /**
7751 * Removes the specified bidirectional segment listener.
7752 *
7753 * @param listener the listener which should no longer be notified
7754 *
7755 * @exception SWTException <ul>
7756 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7757 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7758 * </ul>
7759 * @exception IllegalArgumentException <ul>
7760 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7761 * </ul>
7762 *
7763 * @since 2.0
7764 */
removeBidiSegmentListener(BidiSegmentListener listener)7765 public void removeBidiSegmentListener(BidiSegmentListener listener) {
7766 checkWidget();
7767 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7768 removeListener(ST.LineGetSegments, listener);
7769 resetCache(0, content.getLineCount());
7770 setCaretLocation();
7771 super.redraw();
7772 }
7773 /**
7774 * Removes the specified caret listener.
7775 *
7776 * @param listener the listener which should no longer be notified
7777 *
7778 * @exception SWTException <ul>
7779 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7780 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7781 * </ul>
7782 * @exception IllegalArgumentException <ul>
7783 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7784 * </ul>
7785 *
7786 * @since 3.5
7787 */
removeCaretListener(CaretListener listener)7788 public void removeCaretListener(CaretListener listener) {
7789 checkWidget();
7790 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7791 removeListener(ST.CaretMoved, listener);
7792 }
7793 /**
7794 * Removes the specified extended modify listener.
7795 *
7796 * @param extendedModifyListener the listener which should no longer be notified
7797 *
7798 * @exception SWTException <ul>
7799 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7800 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7801 * </ul>
7802 * @exception IllegalArgumentException <ul>
7803 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7804 * </ul>
7805 */
removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener)7806 public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
7807 checkWidget();
7808 if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7809 removeListener(ST.ExtendedModify, extendedModifyListener);
7810 }
7811 /**
7812 * Removes the specified line background listener.
7813 *
7814 * @param listener the listener which should no longer be notified
7815 *
7816 * @exception SWTException <ul>
7817 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7818 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7819 * </ul>
7820 * @exception IllegalArgumentException <ul>
7821 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7822 * </ul>
7823 */
removeLineBackgroundListener(LineBackgroundListener listener)7824 public void removeLineBackgroundListener(LineBackgroundListener listener) {
7825 checkWidget();
7826 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7827 removeListener(ST.LineGetBackground, listener);
7828 }
7829 /**
7830 * Removes the specified line style listener.
7831 *
7832 * @param listener the listener which should no longer be notified
7833 *
7834 * @exception SWTException <ul>
7835 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7836 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7837 * </ul>
7838 * @exception IllegalArgumentException <ul>
7839 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7840 * </ul>
7841 */
removeLineStyleListener(LineStyleListener listener)7842 public void removeLineStyleListener(LineStyleListener listener) {
7843 checkWidget();
7844 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7845 removeListener(ST.LineGetStyle, listener);
7846 setCaretLocation();
7847 }
7848 /**
7849 * Removes the specified modify listener.
7850 *
7851 * @param modifyListener the listener which should no longer be notified
7852 *
7853 * @exception SWTException <ul>
7854 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7855 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7856 * </ul>
7857 * @exception IllegalArgumentException <ul>
7858 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7859 * </ul>
7860 */
removeModifyListener(ModifyListener modifyListener)7861 public void removeModifyListener(ModifyListener modifyListener) {
7862 checkWidget();
7863 if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7864 removeListener(SWT.Modify, modifyListener);
7865 }
7866 /**
7867 * Removes the specified listener.
7868 *
7869 * @param listener the listener which should no longer be notified
7870 *
7871 * @exception SWTException <ul>
7872 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7873 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7874 * </ul>
7875 * @exception IllegalArgumentException <ul>
7876 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7877 * </ul>
7878 * @since 3.2
7879 */
removePaintObjectListener(PaintObjectListener listener)7880 public void removePaintObjectListener(PaintObjectListener listener) {
7881 checkWidget();
7882 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7883 removeListener(ST.PaintObject, listener);
7884 }
7885 /**
7886 * Removes the listener from the collection of listeners who will
7887 * be notified when the user changes the receiver's selection.
7888 *
7889 * @param listener the listener which should no longer be notified
7890 *
7891 * @exception IllegalArgumentException <ul>
7892 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
7893 * </ul>
7894 * @exception SWTException <ul>
7895 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7896 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7897 * </ul>
7898 *
7899 * @see SelectionListener
7900 * @see #addSelectionListener
7901 */
removeSelectionListener(SelectionListener listener)7902 public void removeSelectionListener(SelectionListener listener) {
7903 checkWidget();
7904 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7905 removeListener(SWT.Selection, listener);
7906 }
7907 /**
7908 * Removes the specified verify listener.
7909 *
7910 * @param verifyListener the listener which should no longer be notified
7911 *
7912 * @exception SWTException <ul>
7913 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7914 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7915 * </ul>
7916 * @exception IllegalArgumentException <ul>
7917 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7918 * </ul>
7919 */
removeVerifyListener(VerifyListener verifyListener)7920 public void removeVerifyListener(VerifyListener verifyListener) {
7921 checkWidget();
7922 if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7923 removeListener(SWT.Verify, verifyListener);
7924 }
7925 /**
7926 * Removes the specified key verify listener.
7927 *
7928 * @param listener the listener which should no longer be notified
7929 *
7930 * @exception SWTException <ul>
7931 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7932 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7933 * </ul>
7934 * @exception IllegalArgumentException <ul>
7935 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7936 * </ul>
7937 */
removeVerifyKeyListener(VerifyKeyListener listener)7938 public void removeVerifyKeyListener(VerifyKeyListener listener) {
7939 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7940 removeListener(ST.VerifyKey, listener);
7941 }
7942 /**
7943 * Removes the specified word movement listener.
7944 *
7945 * @param listener the listener which should no longer be notified
7946 *
7947 * @exception SWTException <ul>
7948 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7949 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7950 * </ul>
7951 * @exception IllegalArgumentException <ul>
7952 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7953 * </ul>
7954 *
7955 * @see MovementEvent
7956 * @see MovementListener
7957 * @see #addWordMovementListener
7958 *
7959 * @since 3.3
7960 */
7961
removeWordMovementListener(MovementListener listener)7962 public void removeWordMovementListener(MovementListener listener) {
7963 checkWidget();
7964 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7965 removeListener(ST.WordNext, listener);
7966 removeListener(ST.WordPrevious, listener);
7967 }
7968 /**
7969 * Replaces the styles in the given range with new styles. This method
7970 * effectively deletes the styles in the given range and then adds the
7971 * the new styles.
7972 * <p>
7973 * Note: Because a StyleRange includes the start and length, the
7974 * same instance cannot occur multiple times in the array of styles.
7975 * If the same style attributes, such as font and color, occur in
7976 * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
7977 * can be used to share styles and reduce memory usage.
7978 * </p><p>
7979 * Should not be called if a LineStyleListener has been set since the
7980 * listener maintains the styles.
7981 * </p>
7982 *
7983 * @param start offset of first character where styles will be deleted
7984 * @param length length of the range to delete styles in
7985 * @param ranges StyleRange objects containing the new style information.
7986 * The ranges should not overlap and should be within the specified start
7987 * and length. The style rendering is undefined if the ranges do overlap
7988 * or are ill-defined. Must not be null.
7989 * @exception SWTException <ul>
7990 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7991 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7992 * </ul>
7993 * @exception IllegalArgumentException <ul>
7994 * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
7995 * <li>ERROR_NULL_ARGUMENT when ranges is null</li>
7996 * </ul>
7997 *
7998 * @since 2.0
7999 *
8000 * @see #setStyleRanges(int, int, int[], StyleRange[])
8001 */
replaceStyleRanges(int start, int length, StyleRange[] ranges)8002 public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
8003 checkWidget();
8004 if (isListening(ST.LineGetStyle)) return;
8005 if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
8006 setStyleRanges(start, length, null, ranges, false);
8007 }
8008 /**
8009 * Replaces the given text range with new text.
8010 * If the widget has the SWT.SINGLE style and "text" contains more than
8011 * one line, only the first line is rendered but the text is stored
8012 * unchanged. A subsequent call to getText will return the same text
8013 * that was set. Note that only a single line of text should be set when
8014 * the SWT.SINGLE style is used.
8015 * <p>
8016 * <b>NOTE:</b> During the replace operation the current selection is
8017 * changed as follows:
8018 * </p>
8019 * <ul>
8020 * <li>selection before replaced text: selection unchanged
8021 * <li>selection after replaced text: adjust the selection so that same text
8022 * remains selected
8023 * <li>selection intersects replaced text: selection is cleared and caret
8024 * is placed after inserted text
8025 * </ul>
8026 *
8027 * @param start offset of first character to replace
8028 * @param length number of characters to replace. Use 0 to insert text
8029 * @param text new text. May be empty to delete text.
8030 * @exception SWTException <ul>
8031 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8032 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8033 * </ul>
8034 * @exception IllegalArgumentException <ul>
8035 * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
8036 * <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
8037 * Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li>
8038 * <li>ERROR_NULL_ARGUMENT when string is null</li>
8039 * </ul>
8040 */
replaceTextRange(int start, int length, String text)8041 public void replaceTextRange(int start, int length, String text) {
8042 checkWidget();
8043 if (text == null) {
8044 SWT.error(SWT.ERROR_NULL_ARGUMENT);
8045 }
8046 int contentLength = getCharCount();
8047 int end = start + length;
8048 if (start > end || start < 0 || end > contentLength) {
8049 SWT.error(SWT.ERROR_INVALID_RANGE);
8050 }
8051 Event event = new Event();
8052 event.start = start;
8053 event.end = end;
8054 event.text = text;
8055 modifyContent(event, false);
8056 }
8057 /**
8058 * Resets the caret position, selection and scroll offsets. Recalculate
8059 * the content width and scroll bars. Redraw the widget.
8060 */
reset()8061 void reset() {
8062 ScrollBar verticalBar = getVerticalBar();
8063 ScrollBar horizontalBar = getHorizontalBar();
8064 setCaretOffset(0, SWT.DEFAULT);
8065 topIndex = 0;
8066 topIndexY = 0;
8067 verticalScrollOffset = 0;
8068 horizontalScrollOffset = 0;
8069 resetSelection();
8070 renderer.setContent(content);
8071 if (verticalBar != null) {
8072 verticalBar.setSelection(0);
8073 }
8074 if (horizontalBar != null) {
8075 horizontalBar.setSelection(0);
8076 }
8077 resetCache(0, 0);
8078 setCaretLocation();
8079 super.redraw();
8080 }
resetBidiData()8081 void resetBidiData() {
8082 caretDirection = SWT.NULL;
8083 resetCache(0, content.getLineCount());
8084 setCaretLocation();
8085 keyActionMap.clear();
8086 createKeyBindings();
8087 super.redraw();
8088 }
resetCache(SortedSet<Integer> lines)8089 void resetCache(SortedSet<Integer> lines) {
8090 if (lines == null || lines.isEmpty()) return;
8091 int maxLineIndex = renderer.maxWidthLineIndex;
8092 renderer.reset(lines);
8093 renderer.calculateClientArea();
8094 if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
8095 renderer.calculate(maxLineIndex, 1);
8096 }
8097 setScrollBars(true);
8098 if (!isFixedLineHeight()) {
8099 if (topIndex > lines.iterator().next()) {
8100 verticalScrollOffset = -1;
8101 }
8102 renderer.calculateIdle();
8103 }
8104 }
resetCache(int firstLine, int count)8105 void resetCache(int firstLine, int count) {
8106 int maxLineIndex = renderer.maxWidthLineIndex;
8107 renderer.reset(firstLine, count);
8108 renderer.calculateClientArea();
8109 if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
8110 renderer.calculate(maxLineIndex, 1);
8111 }
8112 setScrollBars(true);
8113 if (!isFixedLineHeight()) {
8114 if (topIndex > firstLine) {
8115 verticalScrollOffset = -1;
8116 }
8117 renderer.calculateIdle();
8118 }
8119 }
8120 /**
8121 * Resets the selection.
8122 */
resetSelection()8123 void resetSelection() {
8124 selection.x = selection.y = caretOffset;
8125 selectionAnchor = -1;
8126 sendAccessibleTextCaretMoved();
8127 }
8128
8129 @Override
scroll(int destX, int destY, int x, int y, int width, int height, boolean all)8130 public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) {
8131 super.scroll(destX, destY, x, y, width, height, false);
8132 if (all) {
8133 int deltaX = destX - x, deltaY = destY - y;
8134 for (Control child : getChildren()) {
8135 Rectangle rect = child.getBounds();
8136 child.setLocation(rect.x + deltaX, rect.y + deltaY);
8137 }
8138 }
8139 }
8140
8141 /**
8142 * Scrolls the widget horizontally.
8143 *
8144 * @param pixels number of SWT logical points to scroll, > 0 = scroll left,
8145 * < 0 scroll right
8146 * @param adjustScrollBar
8147 * true= the scroll thumb will be moved to reflect the new scroll offset.
8148 * false = the scroll thumb will not be moved
8149 * @return
8150 * true=the widget was scrolled
8151 * false=the widget was not scrolled, the given offset is not valid.
8152 */
scrollHorizontal(int pixels, boolean adjustScrollBar)8153 boolean scrollHorizontal(int pixels, boolean adjustScrollBar) {
8154 if (pixels == 0) return false;
8155 if (wordWrap) return false;
8156 ScrollBar horizontalBar = getHorizontalBar();
8157 if (horizontalBar != null && adjustScrollBar) {
8158 horizontalBar.setSelection(horizontalScrollOffset + pixels);
8159 }
8160 int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
8161 if (pixels > 0) {
8162 int sourceX = leftMargin + pixels;
8163 int scrollWidth = clientAreaWidth - sourceX - rightMargin;
8164 if (scrollWidth > 0) {
8165 scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
8166 }
8167 if (sourceX > scrollWidth) {
8168 super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
8169 }
8170 } else {
8171 int destinationX = leftMargin - pixels;
8172 int scrollWidth = clientAreaWidth - destinationX - rightMargin;
8173 if (scrollWidth > 0) {
8174 scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
8175 }
8176 if (destinationX > scrollWidth) {
8177 super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
8178 }
8179 }
8180 horizontalScrollOffset += pixels;
8181 setCaretLocation();
8182 return true;
8183 }
8184 /**
8185 * Scrolls the widget vertically.
8186 *
8187 * @param pixel the new vertical scroll offset
8188 * @param adjustScrollBar
8189 * true= the scroll thumb will be moved to reflect the new scroll offset.
8190 * false = the scroll thumb will not be moved
8191 * @return
8192 * true=the widget was scrolled
8193 * false=the widget was not scrolled
8194 */
scrollVertical(int pixels, boolean adjustScrollBar)8195 boolean scrollVertical(int pixels, boolean adjustScrollBar) {
8196 if (pixels == 0) {
8197 return false;
8198 }
8199 if (verticalScrollOffset != -1) {
8200 ScrollBar verticalBar = getVerticalBar();
8201 if (verticalBar != null && adjustScrollBar) {
8202 verticalBar.setSelection(verticalScrollOffset + pixels);
8203 }
8204 int deltaY = 0;
8205 if (pixels > 0) {
8206 int sourceY = topMargin + pixels;
8207 int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
8208 if (scrollHeight > 0) {
8209 deltaY = -pixels;
8210 }
8211 } else {
8212 int destinationY = topMargin - pixels;
8213 int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
8214 if (scrollHeight > 0) {
8215 deltaY = -pixels;
8216 }
8217 }
8218 Control[] children = getChildren();
8219 for (Control child : children) {
8220 Rectangle rect = child.getBounds();
8221 child.setLocation(rect.x, rect.y + deltaY);
8222 }
8223 verticalScrollOffset += pixels;
8224 calculateTopIndex(pixels);
8225 super.redraw();
8226 } else {
8227 calculateTopIndex(pixels);
8228 super.redraw();
8229 }
8230 setCaretLocation();
8231 return true;
8232 }
scrollText(int srcY, int destY)8233 void scrollText(int srcY, int destY) {
8234 if (srcY == destY) return;
8235 int deltaY = destY - srcY;
8236 int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
8237 if (deltaY > 0) {
8238 scrollHeight = clientAreaHeight - srcY - bottomMargin;
8239 } else {
8240 scrollHeight = clientAreaHeight - destY - bottomMargin;
8241 }
8242 scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
8243 if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
8244 super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
8245 }
8246 if ((0 < destY + scrollHeight) && (topMargin > destY)) {
8247 super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
8248 }
8249 if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
8250 super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
8251 }
8252 if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
8253 super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
8254 }
8255 }
sendAccessibleTextCaretMoved()8256 void sendAccessibleTextCaretMoved() {
8257 if (caretOffset != accCaretOffset) {
8258 accCaretOffset = caretOffset;
8259 getAccessible().textCaretMoved(caretOffset);
8260 }
8261 }
sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount)8262 void sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount) {
8263 Accessible accessible = getAccessible();
8264 if (replaceCharCount != 0) {
8265 accessible.textChanged(ACC.TEXT_DELETE, start, replaceCharCount);
8266 }
8267 if (newCharCount != 0) {
8268 accessible.textChanged(ACC.TEXT_INSERT, start, newCharCount);
8269 }
8270 }
8271 /**
8272 * Selects all the text.
8273 *
8274 * @exception SWTException <ul>
8275 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8276 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8277 * </ul>
8278 */
selectAll()8279 public void selectAll() {
8280 checkWidget();
8281 if (blockSelection) {
8282 renderer.calculate(0, content.getLineCount());
8283 setScrollBars(false);
8284 int verticalScrollOffset = getVerticalScrollOffset();
8285 int left = leftMargin - horizontalScrollOffset;
8286 int top = topMargin - verticalScrollOffset;
8287 int right = renderer.getWidth() - rightMargin - horizontalScrollOffset;
8288 int bottom = renderer.getHeight() - bottomMargin - verticalScrollOffset;
8289 setBlockSelectionLocation(left, top, right, bottom, false);
8290 return;
8291 }
8292 setSelection(0, Math.max(getCharCount(),0));
8293 }
8294 /**
8295 * Replaces/inserts text as defined by the event.
8296 *
8297 * @param event the text change event.
8298 * <ul>
8299 * <li>event.start - the replace start offset</li>
8300 * <li>event.end - the replace end offset</li>
8301 * <li>event.text - the new text</li>
8302 * </ul>
8303 */
sendKeyEvent(Event event)8304 void sendKeyEvent(Event event) {
8305 if (editable) {
8306 modifyContent(event, true);
8307 }
8308 }
8309 /**
8310 * Returns a StyledTextEvent that can be used to request data such
8311 * as styles and background color for a line.
8312 * <p>
8313 * The specified line may be a visual (wrapped) line if in word
8314 * wrap mode. The returned object will always be for a logical
8315 * (unwrapped) line.
8316 * </p>
8317 *
8318 * @param lineOffset offset of the line. This may be the offset of
8319 * a visual line if the widget is in word wrap mode.
8320 * @param line line text. This may be the text of a visual line if
8321 * the widget is in word wrap mode.
8322 * @return StyledTextEvent that can be used to request line data
8323 * for the given line.
8324 */
sendLineEvent(int eventType, int lineOffset, String line)8325 StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
8326 StyledTextEvent event = null;
8327 if (isListening(eventType)) {
8328 event = new StyledTextEvent(content);
8329 event.detail = lineOffset;
8330 event.text = line;
8331 event.alignment = alignment;
8332 event.indent = indent;
8333 event.wrapIndent = wrapIndent;
8334 event.justify = justify;
8335 notifyListeners(eventType, event);
8336 }
8337 return event;
8338 }
8339 /**
8340 * Sends the specified selection event.
8341 */
sendSelectionEvent()8342 void sendSelectionEvent() {
8343 getAccessible().textSelectionChanged();
8344 Event event = new Event();
8345 event.x = selection.x;
8346 event.y = selection.y;
8347 notifyListeners(SWT.Selection, event);
8348 }
sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces)8349 int sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces) {
8350 int lineWidth = 0, start, end;
8351 StringBuilder buffer = new StringBuilder();
8352 if (lineIndex < content.getLineCount()) {
8353 int[] trailing = new int[1];
8354 start = getOffsetAtPoint(left, getLinePixel(lineIndex), trailing, true);
8355 if (start == -1) {
8356 int lineOffset = content.getOffsetAtLine(lineIndex);
8357 int lineLegth = content.getLine(lineIndex).length();
8358 start = end = lineOffset + lineLegth;
8359 if (fillWithSpaces) {
8360 TextLayout layout = renderer.getTextLayout(lineIndex);
8361 lineWidth = layout.getBounds().width;
8362 renderer.disposeTextLayout(layout);
8363 }
8364 } else {
8365 start += trailing[0];
8366 end = left == right ? start : getOffsetAtPoint(right, 0, lineIndex, null);
8367 fillWithSpaces = false;
8368 }
8369 } else {
8370 start = end = content.getCharCount();
8371 buffer.append(content.getLineDelimiter());
8372 }
8373 if (start > end) {
8374 int temp = start;
8375 start = end;
8376 end = temp;
8377 }
8378 if (fillWithSpaces) {
8379 int spacesWidth = left - lineWidth + horizontalScrollOffset - leftMargin;
8380 int spacesCount = spacesWidth / renderer.averageCharWidth;
8381 for (int i = 0; i < spacesCount; i++) {
8382 buffer.append(' ');
8383 }
8384 }
8385 buffer.append(text);
8386 Event event = new Event();
8387 event.start = start;
8388 event.end = end;
8389 event.text = buffer.toString();
8390 sendKeyEvent(event);
8391 return event.start + event.text.length();
8392 }
sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset)8393 int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) {
8394 if (isListening(eventType)) {
8395 StyledTextEvent event = new StyledTextEvent(content);
8396 event.detail = lineOffset;
8397 event.text = lineText;
8398 event.count = movement;
8399 event.start = offset;
8400 event.end = newOffset;
8401 notifyListeners(eventType, event);
8402 offset = event.end;
8403 if (offset != newOffset) {
8404 int length = getCharCount();
8405 if (offset < 0) {
8406 offset = 0;
8407 } else if (offset > length) {
8408 offset = length;
8409 } else {
8410 if (isLineDelimiter(offset)) {
8411 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8412 }
8413 }
8414 }
8415 return offset;
8416 }
8417 return newOffset;
8418 }
setAlignment()8419 void setAlignment() {
8420 if ((getStyle() & SWT.SINGLE) == 0) return;
8421 int alignment = renderer.getLineAlignment(0, this.alignment);
8422 int newAlignmentMargin = 0;
8423 if (alignment != SWT.LEFT) {
8424 renderer.calculate(0, 1);
8425 int width = renderer.getWidth() - alignmentMargin;
8426 newAlignmentMargin = clientAreaWidth - width;
8427 if (newAlignmentMargin < 0) newAlignmentMargin = 0;
8428 if (alignment == SWT.CENTER) newAlignmentMargin /= 2;
8429 }
8430 if (alignmentMargin != newAlignmentMargin) {
8431 leftMargin -= alignmentMargin;
8432 leftMargin += newAlignmentMargin;
8433 alignmentMargin = newAlignmentMargin;
8434 resetCache(0, 1);
8435 setCaretLocation();
8436 super.redraw();
8437 }
8438 }
8439 /**
8440 * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>,
8441 * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines.
8442 * <p>
8443 * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
8444 * in order to stabilize the right edge before setting alignment.
8445 * </p>
8446 *
8447 * @param alignment the new alignment
8448 *
8449 * @exception SWTException <ul>
8450 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8451 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8452 * </ul>
8453 *
8454 * @see #setLineAlignment(int, int, int)
8455 *
8456 * @since 3.2
8457 */
setAlignment(int alignment)8458 public void setAlignment(int alignment) {
8459 checkWidget();
8460 alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
8461 if (alignment == 0 || this.alignment == alignment) return;
8462 this.alignment = alignment;
8463 resetCache(0, content.getLineCount());
8464 setCaretLocation();
8465 setAlignment();
8466 super.redraw();
8467 }
8468 /**
8469 * Set the Always Show Scrollbars flag. True if the scrollbars are
8470 * always shown even if they are not required (default value). False if the scrollbars are only
8471 * visible when some part of the content needs to be scrolled to be seen.
8472 * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
8473 * horizontal and vertical directions.
8474 *
8475 * @param show true to show the scrollbars even when not required, false to show scrollbars only when required
8476 *
8477 * @exception SWTException <ul>
8478 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8479 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8480 * </ul>
8481 *
8482 * @since 3.8
8483 */
setAlwaysShowScrollBars(boolean show)8484 public void setAlwaysShowScrollBars(boolean show) {
8485 checkWidget();
8486 if (show == alwaysShowScroll) return;
8487 alwaysShowScroll = show;
8488 setScrollBars(true);
8489 }
8490 /**
8491 * @see Control#setBackground(Color)
8492 */
8493 @Override
setBackground(Color color)8494 public void setBackground(Color color) {
8495 checkWidget();
8496 boolean backgroundDisabled = false;
8497 if (!this.enabled && color == null) {
8498 if (background != null) {
8499 Color disabledBg = getDisplay().getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND);
8500 if (background.equals(disabledBg)) {
8501 return;
8502 } else {
8503 color = new Color (getDisplay(), disabledBg.getRGBA());
8504 backgroundDisabled = true;
8505 }
8506 }
8507 }
8508 customBackground = color != null && !this.insideSetEnableCall && !backgroundDisabled;
8509 background = color;
8510 super.setBackground(color);
8511 resetCache(0, content.getLineCount());
8512 setCaretLocation();
8513 super.redraw();
8514 }
8515 /**
8516 * Sets the block selection mode.
8517 *
8518 * @param blockSelection true=enable block selection, false=disable block selection
8519 *
8520 * @since 3.5
8521 */
setBlockSelection(boolean blockSelection)8522 public void setBlockSelection(boolean blockSelection) {
8523 checkWidget();
8524 if ((getStyle() & SWT.SINGLE) != 0) return;
8525 if (blockSelection == this.blockSelection) return;
8526 if (wordWrap) return;
8527 this.blockSelection = blockSelection;
8528 if (cursor == null) {
8529 Display display = getDisplay();
8530 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
8531 super.setCursor(display.getSystemCursor(type));
8532 }
8533 if (blockSelection) {
8534 int start = selection.x;
8535 int end = selection.y;
8536 if (start != end) {
8537 setBlockSelectionOffset(start, end, false);
8538 }
8539 } else {
8540 clearBlockSelection(false, false);
8541 }
8542 }
8543 /**
8544 * Sets the block selection bounds. The bounds is
8545 * relative to the upper left corner of the document.
8546 *
8547 * @param rect the new bounds for the block selection
8548 *
8549 * @see #setBlockSelectionBounds(int, int, int, int)
8550 * @exception SWTException <ul>
8551 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8552 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8553 * </ul>
8554 * @exception IllegalArgumentException <ul>
8555 * <li>ERROR_NULL_ARGUMENT when point is null</li>
8556 * </ul>
8557 *
8558 * @since 3.5
8559 */
setBlockSelectionBounds(Rectangle rect)8560 public void setBlockSelectionBounds(Rectangle rect) {
8561 checkWidget();
8562 if (rect == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
8563 setBlockSelectionBounds(rect.x, rect.y, rect.width, rect.height);
8564 }
8565 /**
8566 * Sets the block selection bounds. The bounds is
8567 * relative to the upper left corner of the document.
8568 *
8569 * @param x the new x coordinate for the block selection
8570 * @param y the new y coordinate for the block selection
8571 * @param width the new width for the block selection
8572 * @param height the new height for the block selection
8573 *
8574 * @exception SWTException <ul>
8575 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8576 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8577 * </ul>
8578 *
8579 * @since 3.5
8580 */
setBlockSelectionBounds(int x, int y, int width, int height)8581 public void setBlockSelectionBounds(int x, int y, int width, int height) {
8582 checkWidget();
8583 int verticalScrollOffset = getVerticalScrollOffset();
8584 if (!blockSelection) {
8585 x -= horizontalScrollOffset;
8586 y -= verticalScrollOffset;
8587 int start = getOffsetAtPoint(x, y, null);
8588 int end = getOffsetAtPoint(x+width-1, y+height-1, null);
8589 setSelection(start, end - start, false, false);
8590 setCaretLocation();
8591 return;
8592 }
8593 int minY = topMargin;
8594 int minX = leftMargin;
8595 int maxY = renderer.getHeight() - bottomMargin;
8596 int maxX = Math.max(clientAreaWidth, renderer.getWidth()) - rightMargin;
8597 int anchorX = Math.max(minX, Math.min(maxX, x)) - horizontalScrollOffset;
8598 int anchorY = Math.max(minY, Math.min(maxY, y)) - verticalScrollOffset;
8599 int locationX = Math.max(minX, Math.min(maxX, x + width)) - horizontalScrollOffset;
8600 int locationY = Math.max(minY, Math.min(maxY, y + height - 1)) - verticalScrollOffset;
8601 if (isFixedLineHeight() && renderer.fixedPitch) {
8602 int avg = renderer.averageCharWidth;
8603 anchorX = ((anchorX - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
8604 locationX = ((locationX + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
8605 }
8606 setBlockSelectionLocation(anchorX, anchorY, locationX, locationY, false);
8607 }
setBlockSelectionLocation(int x, int y, boolean sendEvent)8608 void setBlockSelectionLocation (int x, int y, boolean sendEvent) {
8609 int verticalScrollOffset = getVerticalScrollOffset();
8610 blockXLocation = x + horizontalScrollOffset;
8611 blockYLocation = y + verticalScrollOffset;
8612 int[] alignment = new int[1];
8613 int offset = getOffsetAtPoint(x, y, alignment);
8614 setCaretOffset(offset, alignment[0]);
8615 if (blockXAnchor == -1) {
8616 blockXAnchor = blockXLocation;
8617 blockYAnchor = blockYLocation;
8618 selectionAnchor = caretOffset;
8619 }
8620 doBlockSelection(sendEvent);
8621 }
setBlockSelectionLocation(int anchorX, int anchorY, int x, int y, boolean sendEvent)8622 void setBlockSelectionLocation (int anchorX, int anchorY, int x, int y, boolean sendEvent) {
8623 int verticalScrollOffset = getVerticalScrollOffset();
8624 blockXAnchor = anchorX + horizontalScrollOffset;
8625 blockYAnchor = anchorY + verticalScrollOffset;
8626 selectionAnchor = getOffsetAtPoint(anchorX, anchorY, null);
8627 setBlockSelectionLocation(x, y, sendEvent);
8628 }
setBlockSelectionOffset(int offset, boolean sendEvent)8629 void setBlockSelectionOffset (int offset, boolean sendEvent) {
8630 Point point = getPointAtOffset(offset);
8631 int verticalScrollOffset = getVerticalScrollOffset();
8632 blockXLocation = point.x + horizontalScrollOffset;
8633 blockYLocation = point.y + verticalScrollOffset;
8634 setCaretOffset(offset, SWT.DEFAULT);
8635 if (blockXAnchor == -1) {
8636 blockXAnchor = blockXLocation;
8637 blockYAnchor = blockYLocation;
8638 selectionAnchor = caretOffset;
8639 }
8640 doBlockSelection(sendEvent);
8641 }
setBlockSelectionOffset(int anchorOffset, int offset, boolean sendEvent)8642 void setBlockSelectionOffset (int anchorOffset, int offset, boolean sendEvent) {
8643 int verticalScrollOffset = getVerticalScrollOffset();
8644 Point anchorPoint = getPointAtOffset(anchorOffset);
8645 blockXAnchor = anchorPoint.x + horizontalScrollOffset;
8646 blockYAnchor = anchorPoint.y + verticalScrollOffset;
8647 selectionAnchor = anchorOffset;
8648 setBlockSelectionOffset(offset, sendEvent);
8649 }
8650 /**
8651 * Sets the receiver's caret. Set the caret's height and location.
8652 *
8653 * @param caret the new caret for the receiver
8654 *
8655 * @exception SWTException <ul>
8656 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8657 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8658 * </ul>
8659 */
8660 @Override
setCaret(Caret caret)8661 public void setCaret(Caret caret) {
8662 checkWidget ();
8663 super.setCaret(caret);
8664 caretDirection = SWT.NULL;
8665 if (caret != null) {
8666 setCaretLocation();
8667 }
8668 }
8669 /**
8670 * Sets the BIDI coloring mode. When true the BIDI text display
8671 * algorithm is applied to segments of text that are the same
8672 * color.
8673 *
8674 * @param mode the new coloring mode
8675 * @exception SWTException <ul>
8676 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8677 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8678 * </ul>
8679 *
8680 * @deprecated use BidiSegmentListener instead.
8681 */
8682 @Deprecated
setBidiColoring(boolean mode)8683 public void setBidiColoring(boolean mode) {
8684 checkWidget();
8685 bidiColoring = mode;
8686 }
8687 /**
8688 * Sets the bottom margin.
8689 *
8690 * @param bottomMargin the bottom margin.
8691 * @exception SWTException <ul>
8692 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8693 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8694 * </ul>
8695 *
8696 * @since 3.5
8697 */
setBottomMargin(int bottomMargin)8698 public void setBottomMargin (int bottomMargin) {
8699 checkWidget();
8700 setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
8701 }
8702 /**
8703 * Moves the Caret to the current caret offset.
8704 */
setCaretLocation()8705 void setCaretLocation() {
8706 Point newCaretPos = getPointAtOffset(caretOffset);
8707 setCaretLocation(newCaretPos, getCaretDirection());
8708 }
setCaretLocation(final Point location, int direction)8709 void setCaretLocation(final Point location, int direction) {
8710 Caret caret = getCaret();
8711 if (caret != null) {
8712 final boolean isDefaultCaret = caret == defaultCaret;
8713 final StyleRange styleAtOffset = content.getCharCount() > 0 ?
8714 (caretOffset < content.getCharCount() ?
8715 getStyleRangeAtOffset(caretOffset) :
8716 getStyleRangeAtOffset(content.getCharCount() - 1)) : // caret after last char: use last char style
8717 null;
8718
8719 int graphicalLineHeight = getLineHeight(caretOffset);
8720 int caretHeight = getLineHeight();
8721
8722 if (styleAtOffset != null && styleAtOffset.isVariableHeight()) {
8723 if (isDefaultCaret) {
8724 direction = SWT.DEFAULT;
8725 caretHeight = graphicalLineHeight;
8726 } else {
8727 caretHeight = caret.getSize().y;
8728 }
8729 }
8730 if (caretHeight < graphicalLineHeight) {
8731 location.y += (graphicalLineHeight - caretHeight);
8732 }
8733
8734 int imageDirection = direction;
8735 if (isMirrored()) {
8736 if (imageDirection == SWT.LEFT) {
8737 imageDirection = SWT.RIGHT;
8738 } else if (imageDirection == SWT.RIGHT) {
8739 imageDirection = SWT.LEFT;
8740 }
8741 }
8742 if (isDefaultCaret && imageDirection == SWT.RIGHT) {
8743 location.x -= (caret.getSize().x - 1);
8744 }
8745 if (isDefaultCaret) {
8746 caret.setBounds(location.x, location.y, caretWidth, caretHeight);
8747 } else {
8748 caret.setLocation(location);
8749 }
8750 if (direction != caretDirection) {
8751 caretDirection = direction;
8752 if (isDefaultCaret) {
8753 if (imageDirection == SWT.DEFAULT) {
8754 defaultCaret.setImage(null);
8755 } else if (imageDirection == SWT.LEFT) {
8756 defaultCaret.setImage(leftCaretBitmap);
8757 } else if (imageDirection == SWT.RIGHT) {
8758 defaultCaret.setImage(rightCaretBitmap);
8759 }
8760 }
8761 if (caretDirection == SWT.LEFT) {
8762 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
8763 } else if (caretDirection == SWT.RIGHT) {
8764 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
8765 }
8766 }
8767 updateCaretVisibility();
8768 }
8769 columnX = location.x;
8770 }
8771 /**
8772 * Sets the caret offset.
8773 *
8774 * @param offset caret offset, relative to the first character in the text.
8775 * @exception SWTException <ul>
8776 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8777 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8778 * </ul>
8779 * @exception IllegalArgumentException <ul>
8780 * <li>ERROR_INVALID_ARGUMENT when the offset is inside a multi byte line
8781 * delimiter (and thus neither clearly in front of or after the line delimiter)
8782 * </ul>
8783 */
setCaretOffset(int offset)8784 public void setCaretOffset(int offset) {
8785 checkWidget();
8786 int length = getCharCount();
8787 if (length > 0 && offset != caretOffset) {
8788 if (offset < 0) {
8789 offset = 0;
8790 } else if (offset > length) {
8791 offset = length;
8792 } else {
8793 if (isLineDelimiter(offset)) {
8794 // offset is inside a multi byte line delimiter. This is an
8795 // illegal operation and an exception is thrown. Fixes 1GDKK3R
8796 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8797 }
8798 }
8799 setCaretOffset(offset, PREVIOUS_OFFSET_TRAILING);
8800 // clear the selection if the caret is moved.
8801 // don't notify listeners about the selection change.
8802 if (blockSelection) {
8803 clearBlockSelection(true, false);
8804 } else {
8805 clearSelection(false);
8806 }
8807 }
8808 setCaretLocation();
8809 }
setCaretOffset(int offset, int alignment)8810 void setCaretOffset(int offset, int alignment) {
8811 if (caretOffset != offset) {
8812 caretOffset = offset;
8813 if (isListening(ST.CaretMoved)) {
8814 StyledTextEvent event = new StyledTextEvent(content);
8815 event.end = caretOffset;
8816 notifyListeners(ST.CaretMoved, event);
8817 }
8818 }
8819 if (alignment != SWT.DEFAULT) {
8820 caretAlignment = alignment;
8821 }
8822 }
8823 /**
8824 * Copies the specified text range to the clipboard. The text will be placed
8825 * in the clipboard in plain text format and RTF format.
8826 *
8827 * @param start start index of the text
8828 * @param length length of text to place in clipboard
8829 *
8830 * @exception SWTError
8831 * @see org.eclipse.swt.dnd.Clipboard#setContents
8832 */
setClipboardContent(int start, int length, int clipboardType)8833 void setClipboardContent(int start, int length, int clipboardType) throws SWTError {
8834 if (clipboardType == DND.SELECTION_CLIPBOARD && !IS_GTK) return;
8835 TextTransfer plainTextTransfer = TextTransfer.getInstance();
8836 TextWriter plainTextWriter = new TextWriter(start, length);
8837 String plainText = getPlatformDelimitedText(plainTextWriter);
8838 Object[] data;
8839 Transfer[] types;
8840 if (clipboardType == DND.SELECTION_CLIPBOARD) {
8841 data = new Object[]{plainText};
8842 types = new Transfer[]{plainTextTransfer};
8843 } else {
8844 RTFTransfer rtfTransfer = RTFTransfer.getInstance();
8845 RTFWriter rtfWriter = new RTFWriter(start, length);
8846 String rtfText = getPlatformDelimitedText(rtfWriter);
8847 data = new Object[]{rtfText, plainText};
8848 types = new Transfer[]{rtfTransfer, plainTextTransfer};
8849 }
8850 clipboard.setContents(data, types, clipboardType);
8851 }
8852 /**
8853 * Sets the content implementation to use for text storage.
8854 *
8855 * @param newContent StyledTextContent implementation to use for text storage.
8856 * @exception SWTException <ul>
8857 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8858 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8859 * </ul>
8860 * @exception IllegalArgumentException <ul>
8861 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
8862 * </ul>
8863 */
setContent(StyledTextContent newContent)8864 public void setContent(StyledTextContent newContent) {
8865 checkWidget();
8866 if (newContent == null) {
8867 SWT.error(SWT.ERROR_NULL_ARGUMENT);
8868 }
8869 if (content != null) {
8870 content.removeTextChangeListener(textChangeListener);
8871 }
8872 content = newContent;
8873 content.addTextChangeListener(textChangeListener);
8874 reset();
8875 }
8876 /**
8877 * Sets the receiver's cursor to the cursor specified by the
8878 * argument. Overridden to handle the null case since the
8879 * StyledText widget uses an ibeam as its default cursor.
8880 *
8881 * @see Control#setCursor(Cursor)
8882 */
8883 @Override
setCursor(Cursor cursor)8884 public void setCursor (Cursor cursor) {
8885 checkWidget();
8886 if (cursor != null && cursor.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8887 this.cursor = cursor;
8888 if (cursor == null) {
8889 Display display = getDisplay();
8890 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
8891 super.setCursor(display.getSystemCursor(type));
8892 } else {
8893 super.setCursor(cursor);
8894 }
8895 }
8896 /**
8897 * Sets whether the widget implements double click mouse behavior.
8898 *
8899 * @param enable if true double clicking a word selects the word, if false
8900 * double clicks have the same effect as regular mouse clicks.
8901 * @exception SWTException <ul>
8902 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8903 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8904 * </ul>
8905 */
setDoubleClickEnabled(boolean enable)8906 public void setDoubleClickEnabled(boolean enable) {
8907 checkWidget();
8908 doubleClickEnabled = enable;
8909 }
8910 @Override
setDragDetect(boolean dragDetect)8911 public void setDragDetect (boolean dragDetect) {
8912 checkWidget ();
8913 this.dragDetect = dragDetect;
8914 }
8915 /**
8916 * Sets whether the widget content can be edited.
8917 *
8918 * @param editable if true content can be edited, if false content can not be
8919 * edited
8920 * @exception SWTException <ul>
8921 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8922 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8923 * </ul>
8924 */
setEditable(boolean editable)8925 public void setEditable(boolean editable) {
8926 checkWidget();
8927 this.editable = editable;
8928 }
8929 @Override
setEnabled(boolean enabled)8930 public void setEnabled(boolean enabled) {
8931 super.setEnabled(enabled);
8932 Display display = getDisplay();
8933 this.enabled = enabled;
8934 this.insideSetEnableCall = true;
8935 try {
8936 if (enabled) {
8937 if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
8938 if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
8939 } else {
8940 if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND));
8941 if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND));
8942 }
8943 }
8944 finally {
8945 this.insideSetEnableCall = false;
8946 }
8947 }
8948 /**
8949 * Sets a new font to render text with.
8950 * <p>
8951 * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
8952 * and the same baseline as regular fonts.
8953 * </p>
8954 *
8955 * @param font new font
8956 * @exception SWTException <ul>
8957 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8958 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8959 * </ul>
8960 */
8961 @Override
setFont(Font font)8962 public void setFont(Font font) {
8963 checkWidget();
8964 int oldLineHeight = renderer.getLineHeight();
8965 super.setFont(font);
8966 renderer.setFont(getFont(), tabLength);
8967 // keep the same top line visible. fixes 5815
8968 if (isFixedLineHeight()) {
8969 int lineHeight = renderer.getLineHeight();
8970 if (lineHeight != oldLineHeight) {
8971 int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
8972 scrollVertical(vscroll, true);
8973 }
8974 }
8975 resetCache(0, content.getLineCount());
8976 claimBottomFreeSpace();
8977 calculateScrollBars();
8978 if (isBidiCaret()) createCaretBitmaps();
8979 caretDirection = SWT.NULL;
8980 setCaretLocation();
8981 super.redraw();
8982 }
8983 @Override
setForeground(Color color)8984 public void setForeground(Color color) {
8985 checkWidget();
8986 boolean foregroundDisabled = false;
8987 if (!this.enabled && color == null) {
8988 if (foreground != null) {
8989 Color disabledFg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND);
8990 if (foreground.equals(disabledFg)) {
8991 return;
8992 } else {
8993 color = new Color (getDisplay(), disabledFg.getRGBA());
8994 foregroundDisabled = true;
8995 }
8996 }
8997 }
8998 customForeground = color != null && !this.insideSetEnableCall && !foregroundDisabled;
8999 foreground = color;
9000 super.setForeground(color);
9001 resetCache(0, content.getLineCount());
9002 setCaretLocation();
9003 super.redraw();
9004 }
9005 /**
9006 * Sets the horizontal scroll offset relative to the start of the line.
9007 * Do nothing if there is no text set.
9008 * <p>
9009 * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
9010 * widget.
9011 * </p>
9012 *
9013 * @param offset horizontal scroll offset relative to the start
9014 * of the line, measured in character increments starting at 0, if
9015 * equal to 0 the content is not scrolled, if > 0 = the content is scrolled.
9016 * @exception SWTException <ul>
9017 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9018 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9019 * </ul>
9020 */
setHorizontalIndex(int offset)9021 public void setHorizontalIndex(int offset) {
9022 checkWidget();
9023 if (getCharCount() == 0) {
9024 return;
9025 }
9026 if (offset < 0) {
9027 offset = 0;
9028 }
9029 offset *= getHorizontalIncrement();
9030 // allow any value if client area width is unknown or 0.
9031 // offset will be checked in resize handler.
9032 // don't use isVisible since width is known even if widget
9033 // is temporarily invisible
9034 if (clientAreaWidth > 0) {
9035 int width = renderer.getWidth();
9036 // prevent scrolling if the content fits in the client area.
9037 // align end of longest line with right border of client area
9038 // if offset is out of range.
9039 if (offset > width - clientAreaWidth) {
9040 offset = Math.max(0, width - clientAreaWidth);
9041 }
9042 }
9043 scrollHorizontal(offset - horizontalScrollOffset, true);
9044 }
9045 /**
9046 * Sets the horizontal SWT logical point offset relative to the start of the line.
9047 * Do nothing if there is no text set.
9048 * <p>
9049 * <b>NOTE:</b> The horizontal SWT logical point offset is reset to 0 when new text
9050 * is set in the widget.
9051 * </p>
9052 *
9053 * @param pixel horizontal SWT logical point offset relative to the start
9054 * of the line.
9055 * @exception SWTException <ul>
9056 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9057 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9058 * </ul>
9059 * @since 2.0
9060 */
setHorizontalPixel(int pixel)9061 public void setHorizontalPixel(int pixel) {
9062 checkWidget();
9063 if (getCharCount() == 0) {
9064 return;
9065 }
9066 if (pixel < 0) {
9067 pixel = 0;
9068 }
9069 // allow any value if client area width is unknown or 0.
9070 // offset will be checked in resize handler.
9071 // don't use isVisible since width is known even if widget
9072 // is temporarily invisible
9073 if (clientAreaWidth > 0) {
9074 int width = renderer.getWidth();
9075 // prevent scrolling if the content fits in the client area.
9076 // align end of longest line with right border of client area
9077 // if offset is out of range.
9078 if (pixel > width - clientAreaWidth) {
9079 pixel = Math.max(0, width - clientAreaWidth);
9080 }
9081 }
9082 scrollHorizontal(pixel - horizontalScrollOffset, true);
9083 }
9084 /**
9085 * Sets the line indentation of the widget.
9086 * <p>
9087 * It is the amount of blank space, in points, at the beginning of each line.
9088 * When a line wraps in several lines only the first one is indented.
9089 * </p>
9090 *
9091 * @param indent the new indent
9092 *
9093 * @exception SWTException <ul>
9094 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9095 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9096 * </ul>
9097 *
9098 * @see #setLineIndent(int, int, int)
9099 *
9100 * @since 3.2
9101 */
setIndent(int indent)9102 public void setIndent(int indent) {
9103 checkWidget();
9104 if (this.indent == indent || indent < 0) return;
9105 this.indent = indent;
9106 resetCache(0, content.getLineCount());
9107 setCaretLocation();
9108 super.redraw();
9109 }
9110 /**
9111 * Sets whether the widget should justify lines.
9112 *
9113 * @param justify whether lines should be justified
9114 *
9115 * @exception SWTException <ul>
9116 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9117 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9118 * </ul>
9119 *
9120 * @see #setLineJustify(int, int, boolean)
9121 *
9122 * @since 3.2
9123 */
setJustify(boolean justify)9124 public void setJustify(boolean justify) {
9125 checkWidget();
9126 if (this.justify == justify) return;
9127 this.justify = justify;
9128 resetCache(0, content.getLineCount());
9129 setCaretLocation();
9130 super.redraw();
9131 }
9132 /**
9133 * Maps a key to an action.
9134 * <p>
9135 * One action can be associated with N keys. However, each key can only
9136 * have one action (key:action is N:1 relation).
9137 * </p>
9138 *
9139 * @param key a key code defined in SWT.java or a character.
9140 * Optionally ORd with a state mask. Preferred state masks are one or more of
9141 * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
9142 * differences. However, there may be cases where using the specific state masks
9143 * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
9144 * @param action one of the predefined actions defined in ST.java.
9145 * Use SWT.NULL to remove a key binding.
9146 * @exception SWTException <ul>
9147 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9148 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9149 * </ul>
9150 */
setKeyBinding(int key, int action)9151 public void setKeyBinding(int key, int action) {
9152 checkWidget();
9153 int modifierValue = key & SWT.MODIFIER_MASK;
9154 int keyInt = key & SWT.KEY_MASK;
9155 char keyChar = (char)keyInt;
9156 /**
9157 * Bug 440535: Make sure the key getting mapped to letter is in defiened
9158 * character range and filter out incorrect int to char typecasting. For
9159 * Example: SWT.KEYPAD_CR int gets wrongly type-cast to char letter 'p'
9160 */
9161 if (Character.isDefined(keyInt) && Character.isLetter(keyChar)) {
9162 // make the keybinding case insensitive by adding it
9163 // in its upper and lower case form
9164 char ch = Character.toUpperCase(keyChar);
9165 int newKey = ch | modifierValue;
9166 if (action == SWT.NULL) {
9167 keyActionMap.remove(newKey);
9168 } else {
9169 keyActionMap.put(newKey, action);
9170 }
9171 ch = Character.toLowerCase(keyChar);
9172 newKey = ch | modifierValue;
9173 if (action == SWT.NULL) {
9174 keyActionMap.remove(newKey);
9175 } else {
9176 keyActionMap.put(newKey, action);
9177 }
9178 } else {
9179 if (action == SWT.NULL) {
9180 keyActionMap.remove(key);
9181 } else {
9182 keyActionMap.put(key, action);
9183 }
9184 }
9185 }
9186 /**
9187 * Sets the left margin.
9188 *
9189 * @param leftMargin the left margin.
9190 * @exception SWTException <ul>
9191 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9192 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9193 * </ul>
9194 *
9195 * @since 3.5
9196 */
setLeftMargin(int leftMargin)9197 public void setLeftMargin (int leftMargin) {
9198 checkWidget();
9199 setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
9200 }
9201 /**
9202 * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>,
9203 * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>.
9204 * <p>
9205 * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
9206 * in order to stabilize the right edge before setting alignment.
9207 * </p><p>
9208 * Should not be called if a LineStyleListener has been set since the listener
9209 * maintains the line attributes.
9210 * </p><p>
9211 * All line attributes are maintained relative to the line text, not the
9212 * line index that is specified in this method call.
9213 * During text changes, when entire lines are inserted or removed, the line
9214 * attributes that are associated with the lines after the change
9215 * will "move" with their respective text. An entire line is defined as
9216 * extending from the first character on a line to the last and including the
9217 * line delimiter.
9218 * </p>
9219 * When two lines are joined by deleting a line delimiter, the top line
9220 * attributes take precedence and the attributes of the bottom line are deleted.
9221 * For all other text changes line attributes will remain unchanged.
9222 *
9223 * @param startLine first line the alignment is applied to, 0 based
9224 * @param lineCount number of lines the alignment applies to.
9225 * @param alignment line alignment
9226 *
9227 * @exception SWTException <ul>
9228 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9229 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9230 * </ul>
9231 * @exception IllegalArgumentException <ul>
9232 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9233 * </ul>
9234 * @see #setAlignment(int)
9235 * @since 3.2
9236 */
setLineAlignment(int startLine, int lineCount, int alignment)9237 public void setLineAlignment(int startLine, int lineCount, int alignment) {
9238 checkWidget();
9239 if (isListening(ST.LineGetStyle)) return;
9240 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9241 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9242 }
9243
9244 renderer.setLineAlignment(startLine, lineCount, alignment);
9245 resetCache(startLine, lineCount);
9246 redrawLines(startLine, lineCount, false);
9247 int caretLine = getCaretLine();
9248 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9249 setCaretLocation();
9250 }
9251 setAlignment();
9252 }
9253 /**
9254 * Sets the background color of the specified lines.
9255 * <p>
9256 * The background color is drawn for the width of the widget. All
9257 * line background colors are discarded when setText is called.
9258 * The text background color if defined in a StyleRange overlays the
9259 * line background color.
9260 * </p><p>
9261 * Should not be called if a LineBackgroundListener has been set since the
9262 * listener maintains the line backgrounds.
9263 * </p><p>
9264 * All line attributes are maintained relative to the line text, not the
9265 * line index that is specified in this method call.
9266 * During text changes, when entire lines are inserted or removed, the line
9267 * attributes that are associated with the lines after the change
9268 * will "move" with their respective text. An entire line is defined as
9269 * extending from the first character on a line to the last and including the
9270 * line delimiter.
9271 * </p><p>
9272 * When two lines are joined by deleting a line delimiter, the top line
9273 * attributes take precedence and the attributes of the bottom line are deleted.
9274 * For all other text changes line attributes will remain unchanged.
9275 * </p>
9276 *
9277 * @param startLine first line the color is applied to, 0 based
9278 * @param lineCount number of lines the color applies to.
9279 * @param background line background color
9280 * @exception SWTException <ul>
9281 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9282 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9283 * </ul>
9284 * @exception IllegalArgumentException <ul>
9285 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9286 * </ul>
9287 */
setLineBackground(int startLine, int lineCount, Color background)9288 public void setLineBackground(int startLine, int lineCount, Color background) {
9289 checkWidget();
9290 if (isListening(ST.LineGetBackground)) return;
9291 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9292 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9293 }
9294 if (background != null) {
9295 renderer.setLineBackground(startLine, lineCount, background);
9296 } else {
9297 renderer.clearLineBackground(startLine, lineCount);
9298 }
9299 redrawLines(startLine, lineCount, false);
9300 }
9301 /**
9302 * Sets the bullet of the specified lines.
9303 * <p>
9304 * Should not be called if a LineStyleListener has been set since the listener
9305 * maintains the line attributes.
9306 * </p><p>
9307 * All line attributes are maintained relative to the line text, not the
9308 * line index that is specified in this method call.
9309 * During text changes, when entire lines are inserted or removed, the line
9310 * attributes that are associated with the lines after the change
9311 * will "move" with their respective text. An entire line is defined as
9312 * extending from the first character on a line to the last and including the
9313 * line delimiter.
9314 * </p><p>
9315 * When two lines are joined by deleting a line delimiter, the top line
9316 * attributes take precedence and the attributes of the bottom line are deleted.
9317 * For all other text changes line attributes will remain unchanged.
9318 * </p>
9319 *
9320 * @param startLine first line the bullet is applied to, 0 based
9321 * @param lineCount number of lines the bullet applies to.
9322 * @param bullet line bullet
9323 *
9324 * @exception SWTException <ul>
9325 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9326 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9327 * </ul>
9328 * @exception IllegalArgumentException <ul>
9329 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9330 * </ul>
9331 * @since 3.2
9332 */
setLineBullet(int startLine, int lineCount, Bullet bullet)9333 public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
9334 checkWidget();
9335 if (isListening(ST.LineGetStyle)) return;
9336 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9337 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9338 }
9339 int oldBottom = getLinePixel(startLine + lineCount);
9340 renderer.setLineBullet(startLine, lineCount, bullet);
9341 resetCache(startLine, lineCount);
9342 int newBottom = getLinePixel(startLine + lineCount);
9343 redrawLines(startLine, lineCount, oldBottom != newBottom);
9344 int caretLine = getCaretLine();
9345 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9346 setCaretLocation();
9347 }
9348 }
9349 /**
9350 * Returns true if StyledText is in word wrap mode and false otherwise.
9351 *
9352 * @return true if StyledText is in word wrap mode and false otherwise.
9353 */
isWordWrap()9354 boolean isWordWrap() {
9355 return wordWrap || visualWrap;
9356 }
9357 /**
9358 * Sets the indent of the specified lines.
9359 * <p>
9360 * Should not be called if a LineStyleListener has been set since the listener
9361 * maintains the line attributes.
9362 * </p><p>
9363 * All line attributes are maintained relative to the line text, not the
9364 * line index that is specified in this method call.
9365 * During text changes, when entire lines are inserted or removed, the line
9366 * attributes that are associated with the lines after the change
9367 * will "move" with their respective text. An entire line is defined as
9368 * extending from the first character on a line to the last and including the
9369 * line delimiter.
9370 * </p><p>
9371 * When two lines are joined by deleting a line delimiter, the top line
9372 * attributes take precedence and the attributes of the bottom line are deleted.
9373 * For all other text changes line attributes will remain unchanged.
9374 * </p>
9375 *
9376 * @param startLine first line the indent is applied to, 0 based
9377 * @param lineCount number of lines the indent applies to.
9378 * @param indent line indent
9379 *
9380 * @exception SWTException <ul>
9381 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9382 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9383 * </ul>
9384 * @exception IllegalArgumentException <ul>
9385 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9386 * </ul>
9387 * @see #setIndent(int)
9388 * @since 3.2
9389 */
setLineIndent(int startLine, int lineCount, int indent)9390 public void setLineIndent(int startLine, int lineCount, int indent) {
9391 checkWidget();
9392 if (isListening(ST.LineGetStyle)) return;
9393 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9394 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9395 }
9396 int oldBottom = getLinePixel(startLine + lineCount);
9397 renderer.setLineIndent(startLine, lineCount, indent);
9398 resetCache(startLine, lineCount);
9399 int newBottom = getLinePixel(startLine + lineCount);
9400 redrawLines(startLine, lineCount, oldBottom != newBottom);
9401 int caretLine = getCaretLine();
9402 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9403 setCaretLocation();
9404 }
9405 }
9406
9407 /**
9408 * Sets the vertical indent of the specified lines.
9409 * <p>
9410 * Should not be called if a LineStyleListener has been set since the listener
9411 * maintains the line attributes.
9412 * </p><p>
9413 * All line attributes are maintained relative to the line text, not the
9414 * line index that is specified in this method call.
9415 * During text changes, when entire lines are inserted or removed, the line
9416 * attributes that are associated with the lines after the change
9417 * will "move" with their respective text. An entire line is defined as
9418 * extending from the first character on a line to the last and including the
9419 * line delimiter.
9420 * </p><p>
9421 * When two lines are joined by deleting a line delimiter, the top line
9422 * attributes take precedence and the attributes of the bottom line are deleted.
9423 * For all other text changes line attributes will remain unchanged.
9424 * </p><p>
9425 * Setting both line spacing and vertical indent on a line would result in the
9426 * spacing and indent add up for the line.
9427 * </p>
9428 *
9429 * @param lineIndex line index the vertical indent is applied to, 0 based
9430 * @param verticalLineIndent vertical line indent
9431 *
9432 * @exception SWTException <ul>
9433 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9434 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9435 * </ul>
9436 * @exception IllegalArgumentException <ul>
9437 * <li>ERROR_INVALID_ARGUMENT when the specified line index is invalid</li>
9438 * </ul>
9439 * @since 3.109
9440 */
setLineVerticalIndent(int lineIndex, int verticalLineIndent)9441 public void setLineVerticalIndent(int lineIndex, int verticalLineIndent) {
9442 checkWidget();
9443 if (isListening(ST.LineGetStyle)) return;
9444 if (lineIndex < 0 || lineIndex >= content.getLineCount()) {
9445 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9446 }
9447 if (verticalLineIndent == renderer.getLineVerticalIndent(lineIndex)) {
9448 return;
9449 }
9450 int oldBottom = getLinePixel(lineIndex + 1);
9451 if (oldBottom <= getClientArea().height) {
9452 verticalScrollOffset = -1;
9453 }
9454 renderer.setLineVerticalIndent(lineIndex, verticalLineIndent);
9455 hasVerticalIndent = verticalLineIndent != 0 || renderer.hasVerticalIndent();
9456 resetCache(lineIndex, 1);
9457 int newBottom = getLinePixel(lineIndex + 1);
9458 redrawLines(lineIndex, 1, oldBottom != newBottom);
9459 int caretLine = getCaretLine();
9460 if (lineIndex <= caretLine && caretLine < lineIndex + 1) {
9461 setCaretLocation();
9462 }
9463 }
9464
9465 /**
9466 * Sets the justify of the specified lines.
9467 * <p>
9468 * Should not be called if a LineStyleListener has been set since the listener
9469 * maintains the line attributes.
9470 * </p><p>
9471 * All line attributes are maintained relative to the line text, not the
9472 * line index that is specified in this method call.
9473 * During text changes, when entire lines are inserted or removed, the line
9474 * attributes that are associated with the lines after the change
9475 * will "move" with their respective text. An entire line is defined as
9476 * extending from the first character on a line to the last and including the
9477 * line delimiter.
9478 * </p><p>
9479 * When two lines are joined by deleting a line delimiter, the top line
9480 * attributes take precedence and the attributes of the bottom line are deleted.
9481 * For all other text changes line attributes will remain unchanged.
9482 * </p>
9483 *
9484 * @param startLine first line the justify is applied to, 0 based
9485 * @param lineCount number of lines the justify applies to.
9486 * @param justify true if lines should be justified
9487 *
9488 * @exception SWTException <ul>
9489 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9490 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9491 * </ul>
9492 * @exception IllegalArgumentException <ul>
9493 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9494 * </ul>
9495 * @see #setJustify(boolean)
9496 * @since 3.2
9497 */
setLineJustify(int startLine, int lineCount, boolean justify)9498 public void setLineJustify(int startLine, int lineCount, boolean justify) {
9499 checkWidget();
9500 if (isListening(ST.LineGetStyle)) return;
9501 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9502 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9503 }
9504
9505 renderer.setLineJustify(startLine, lineCount, justify);
9506 resetCache(startLine, lineCount);
9507 redrawLines(startLine, lineCount, false);
9508 int caretLine = getCaretLine();
9509 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9510 setCaretLocation();
9511 }
9512 }
9513 /**
9514 * Sets the line spacing of the widget. The line spacing applies for all lines.
9515 * In the case of #setLineSpacingProvider(StyledTextLineSpacingProvider) is customized,
9516 * the line spacing are applied only for the lines which are not managed by {@link StyledTextLineSpacingProvider}.
9517 *
9518 * @param lineSpacing the line spacing
9519 * @exception SWTException <ul>
9520 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9521 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9522 * </ul>
9523 * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
9524 * @since 3.2
9525 */
setLineSpacing(int lineSpacing)9526 public void setLineSpacing(int lineSpacing) {
9527 checkWidget();
9528 if (this.lineSpacing == lineSpacing || lineSpacing < 0) return;
9529 this.lineSpacing = lineSpacing;
9530 resetCache(0, content.getLineCount());
9531 setCaretLocation();
9532 super.redraw();
9533 }
9534 /**
9535 * Sets the line spacing provider of the widget. The line spacing applies for some lines with customized spacing
9536 * or reset the customized spacing if the argument is null.
9537 *
9538 * @param lineSpacingProvider the line spacing provider (or null)
9539 * @exception SWTException <ul>
9540 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9541 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9542 * </ul>
9543 * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
9544 * @since 3.107
9545 */
setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider)9546 public void setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider) {
9547 checkWidget();
9548 boolean wasFixedLineHeight = isFixedLineHeight();
9549 if (renderer.getLineSpacingProvider() == null && lineSpacingProvider == null
9550 || (renderer.getLineSpacingProvider() != null
9551 && renderer.getLineSpacingProvider().equals(lineSpacingProvider)))
9552 return;
9553 renderer.setLineSpacingProvider(lineSpacingProvider);
9554 // reset lines cache if needed
9555 if (lineSpacingProvider == null) {
9556 if (!wasFixedLineHeight) {
9557 resetCache(0, content.getLineCount());
9558 }
9559 } else {
9560 if (wasFixedLineHeight) {
9561 int firstLine = -1;
9562 for (int i = 0; i < content.getLineCount(); i++) {
9563 Integer lineSpacing = lineSpacingProvider.getLineSpacing(i);
9564 if (lineSpacing != null && lineSpacing > 0) {
9565 // there is a custom line spacing, set StyledText as variable line height mode
9566 // reset only the line size
9567 renderer.reset(i, 1);
9568 if (firstLine == -1) {
9569 firstLine = i;
9570 }
9571 }
9572 }
9573 if (firstLine != -1) {
9574 // call reset cache for the first line which have changed to recompute scrollbars
9575 resetCache(firstLine, 0);
9576 }
9577 }
9578 }
9579 setCaretLocation();
9580 super.redraw();
9581 }
9582 /**
9583 * Sets the tab stops of the specified lines.
9584 * <p>
9585 * Should not be called if a <code>LineStyleListener</code> has been set since the listener
9586 * maintains the line attributes.
9587 * </p><p>
9588 * All line attributes are maintained relative to the line text, not the
9589 * line index that is specified in this method call.
9590 * During text changes, when entire lines are inserted or removed, the line
9591 * attributes that are associated with the lines after the change
9592 * will "move" with their respective text. An entire line is defined as
9593 * extending from the first character on a line to the last and including the
9594 * line delimiter.
9595 * </p><p>
9596 * When two lines are joined by deleting a line delimiter, the top line
9597 * attributes take precedence and the attributes of the bottom line are deleted.
9598 * For all other text changes line attributes will remain unchanged.
9599 * </p>
9600 *
9601 * @param startLine first line the justify is applied to, 0 based
9602 * @param lineCount number of lines the justify applies to.
9603 * @param tabStops tab stops
9604 *
9605 * @exception SWTException <ul>
9606 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9607 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9608 * </ul>
9609 * @exception IllegalArgumentException <ul>
9610 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9611 * </ul>
9612 * @see #setTabStops(int[])
9613 * @since 3.6
9614 */
setLineTabStops(int startLine, int lineCount, int[] tabStops)9615 public void setLineTabStops(int startLine, int lineCount, int[] tabStops) {
9616 checkWidget();
9617 if (isListening(ST.LineGetStyle)) return;
9618 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9619 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9620 }
9621 if (tabStops != null) {
9622 int pos = 0;
9623 int[] newTabs = new int[tabStops.length];
9624 for (int i = 0; i < tabStops.length; i++) {
9625 if (tabStops[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9626 newTabs[i] = pos = tabStops[i];
9627 }
9628 renderer.setLineTabStops(startLine, lineCount, newTabs);
9629 } else {
9630 renderer.setLineTabStops(startLine, lineCount, null);
9631 }
9632 resetCache(startLine, lineCount);
9633 redrawLines(startLine, lineCount, false);
9634 int caretLine = getCaretLine();
9635 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9636 setCaretLocation();
9637 }
9638 }
9639 /**
9640 * Sets the wrap indent of the specified lines.
9641 * <p>
9642 * Should not be called if a <code>LineStyleListener</code> has been set since the listener
9643 * maintains the line attributes.
9644 * </p><p>
9645 * All line attributes are maintained relative to the line text, not the
9646 * line index that is specified in this method call.
9647 * During text changes, when entire lines are inserted or removed, the line
9648 * attributes that are associated with the lines after the change
9649 * will "move" with their respective text. An entire line is defined as
9650 * extending from the first character on a line to the last and including the
9651 * line delimiter.
9652 * </p><p>
9653 * When two lines are joined by deleting a line delimiter, the top line
9654 * attributes take precedence and the attributes of the bottom line are deleted.
9655 * For all other text changes line attributes will remain unchanged.
9656 * </p>
9657 *
9658 * @param startLine first line the wrap indent is applied to, 0 based
9659 * @param lineCount number of lines the wrap indent applies to.
9660 * @param wrapIndent line wrap indent
9661 *
9662 * @exception SWTException <ul>
9663 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9664 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9665 * </ul>
9666 * @exception IllegalArgumentException <ul>
9667 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9668 * </ul>
9669 * @see #setWrapIndent(int)
9670 * @since 3.6
9671 */
setLineWrapIndent(int startLine, int lineCount, int wrapIndent)9672 public void setLineWrapIndent(int startLine, int lineCount, int wrapIndent) {
9673 checkWidget();
9674 if (isListening(ST.LineGetStyle)) return;
9675 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9676 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9677 }
9678 int oldBottom = getLinePixel(startLine + lineCount);
9679 renderer.setLineWrapIndent(startLine, lineCount, wrapIndent);
9680 resetCache(startLine, lineCount);
9681 int newBottom = getLinePixel(startLine + lineCount);
9682 redrawLines(startLine, lineCount, oldBottom != newBottom);
9683 int caretLine = getCaretLine();
9684 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9685 setCaretLocation();
9686 }
9687 }
9688
9689 /**
9690 * Sets the color of the margins.
9691 *
9692 * @param color the new color (or null)
9693 * @exception IllegalArgumentException <ul>
9694 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9695 * </ul>
9696 * @exception SWTException <ul>
9697 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9698 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9699 * </ul>
9700 *
9701 * @since 3.5
9702 */
setMarginColor(Color color)9703 public void setMarginColor(Color color) {
9704 checkWidget();
9705 if (color != null && color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9706 marginColor = color;
9707 super.redraw();
9708 }
9709 /**
9710 * Sets the margins.
9711 *
9712 * @param leftMargin the left margin.
9713 * @param topMargin the top margin.
9714 * @param rightMargin the right margin.
9715 * @param bottomMargin the bottom margin.
9716 * @exception SWTException <ul>
9717 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9718 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9719 * </ul>
9720 *
9721 * @since 3.5
9722 */
setMargins(int leftMargin, int topMargin, int rightMargin, int bottomMargin)9723 public void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
9724 checkWidget();
9725 this.leftMargin = Math.max(0, leftMargin) + alignmentMargin;
9726 this.topMargin = Math.max(0, topMargin);
9727 this.rightMargin = Math.max(0, rightMargin);
9728 this.bottomMargin = Math.max(0, bottomMargin);
9729 resetCache(0, content.getLineCount());
9730 setScrollBars(true);
9731 setCaretLocation();
9732 setAlignment();
9733 super.redraw();
9734 }
9735 /**
9736 * Sets the enabled state of the mouse navigator. When the mouse navigator is enabled, the user can navigate through the widget
9737 * by pressing the middle button and moving the cursor.
9738 *
9739 * @param enabled if true, the mouse navigator is enabled, if false the mouse navigator is deactivated
9740 * @exception SWTException <ul>
9741 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9742 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9743 * </ul>
9744 * @since 3.110
9745 */
setMouseNavigatorEnabled(boolean enabled)9746 public void setMouseNavigatorEnabled(boolean enabled) {
9747 checkWidget();
9748 if ((enabled && mouseNavigator != null) || (!enabled && mouseNavigator == null)) {
9749 return;
9750 }
9751 if (enabled) {
9752 mouseNavigator = new MouseNavigator(this);
9753 } else {
9754 mouseNavigator.dispose();
9755 mouseNavigator = null;
9756 }
9757 }
9758 /**
9759 * Flips selection anchor based on word selection direction.
9760 */
setMouseWordSelectionAnchor()9761 void setMouseWordSelectionAnchor() {
9762 if (doubleClickEnabled && clickCount > 1) {
9763 if (caretOffset < doubleClickSelection.x) {
9764 selectionAnchor = doubleClickSelection.y;
9765 } else if (caretOffset > doubleClickSelection.y) {
9766 selectionAnchor = doubleClickSelection.x;
9767 }
9768 }
9769 }
9770 /**
9771 * Sets the orientation of the receiver, which must be one
9772 * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
9773 *
9774 * @param orientation new orientation style
9775 *
9776 * @exception SWTException <ul>
9777 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9778 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9779 * </ul>
9780 *
9781 * @since 2.1.2
9782 */
9783 @Override
setOrientation(int orientation)9784 public void setOrientation(int orientation) {
9785 int oldOrientation = getOrientation();
9786 super.setOrientation(orientation);
9787 int newOrientation = getOrientation();
9788 if (oldOrientation != newOrientation) {
9789 resetBidiData();
9790 }
9791 }
9792 /**
9793 * Sets the right margin.
9794 *
9795 * @param rightMargin the right margin.
9796 * @exception SWTException <ul>
9797 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9798 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9799 * </ul>
9800 *
9801 * @since 3.5
9802 */
setRightMargin(int rightMargin)9803 public void setRightMargin (int rightMargin) {
9804 checkWidget();
9805 setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
9806 }
setScrollBar(ScrollBar bar, int clientArea, int maximum, int margin)9807 void setScrollBar(ScrollBar bar, int clientArea, int maximum, int margin) {
9808 int inactive = 1;
9809 if (clientArea < maximum) {
9810 bar.setMaximum(maximum - margin);
9811 bar.setThumb(clientArea - margin);
9812 bar.setPageIncrement(clientArea - margin);
9813 if (!alwaysShowScroll) bar.setVisible(true);
9814 } else if (bar.getThumb() != inactive || bar.getMaximum() != inactive) {
9815 bar.setValues(bar.getSelection(), bar.getMinimum(), inactive, inactive, bar.getIncrement(), inactive);
9816 }
9817 }
9818 /**
9819 * Adjusts the maximum and the page size of the scroll bars to
9820 * reflect content width/length changes.
9821 *
9822 * @param vertical indicates if the vertical scrollbar also needs to be set
9823 */
setScrollBars(boolean vertical)9824 void setScrollBars(boolean vertical) {
9825 ignoreResize++;
9826 if (!isFixedLineHeight() || !alwaysShowScroll) vertical = true;
9827 ScrollBar verticalBar = vertical ? getVerticalBar() : null;
9828 ScrollBar horizontalBar = getHorizontalBar();
9829 int oldHeight = clientAreaHeight;
9830 int oldWidth = clientAreaWidth;
9831 if (!alwaysShowScroll) {
9832 if (verticalBar != null) verticalBar.setVisible(false);
9833 if (horizontalBar != null) horizontalBar.setVisible(false);
9834 }
9835 if (verticalBar != null) {
9836 setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
9837 }
9838 if (horizontalBar != null && !wordWrap) {
9839 setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
9840 if (!alwaysShowScroll && horizontalBar.getVisible() && verticalBar != null) {
9841 setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
9842 if (verticalBar.getVisible()) {
9843 setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
9844 }
9845 }
9846 }
9847 if (!alwaysShowScroll) {
9848 redrawMargins(oldHeight, oldWidth);
9849 }
9850 ignoreResize--;
9851 }
9852 /**
9853 * Sets the selection to the given position and scrolls it into view. Equivalent to setSelection(start,start).
9854 *
9855 * @param start new caret position
9856 * @see #setSelection(int,int)
9857 * @exception SWTException <ul>
9858 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9859 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9860 * </ul>
9861 * @exception IllegalArgumentException <ul>
9862 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9863 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9864 * </ul>
9865 */
setSelection(int start)9866 public void setSelection(int start) {
9867 // checkWidget test done in setSelectionRange
9868 setSelection(start, start);
9869 }
9870 /**
9871 * Sets the selection and scrolls it into view.
9872 * <p>
9873 * Indexing is zero based. Text selections are specified in terms of
9874 * caret positions. In a text widget that contains N characters, there are
9875 * N+1 caret positions, ranging from 0..N
9876 * </p>
9877 *
9878 * @param point x=selection start offset, y=selection end offset
9879 * The caret will be placed at the selection start when x > y.
9880 * @see #setSelection(int,int)
9881 * @exception SWTException <ul>
9882 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9883 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9884 * </ul>
9885 * @exception IllegalArgumentException <ul>
9886 * <li>ERROR_NULL_ARGUMENT when point is null</li>
9887 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9888 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9889 * </ul>
9890 */
setSelection(Point point)9891 public void setSelection(Point point) {
9892 checkWidget();
9893 if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
9894 setSelection(point.x, point.y);
9895 }
9896 /**
9897 * Sets the receiver's selection background color to the color specified
9898 * by the argument, or to the default system color for the control
9899 * if the argument is null.
9900 *
9901 * @param color the new color (or null)
9902 *
9903 * @exception IllegalArgumentException <ul>
9904 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9905 * </ul>
9906 * @exception SWTException <ul>
9907 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9908 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9909 * </ul>
9910 * @since 2.1
9911 */
setSelectionBackground(Color color)9912 public void setSelectionBackground (Color color) {
9913 checkWidget ();
9914 if (color != null) {
9915 if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9916 }
9917 selectionBackground = color;
9918 resetCache(0, content.getLineCount());
9919 setCaretLocation();
9920 super.redraw();
9921 }
9922 /**
9923 * Sets the receiver's selection foreground color to the color specified
9924 * by the argument, or to the default system color for the control
9925 * if the argument is null.
9926 * <p>
9927 * Note that this is a <em>HINT</em>. Some platforms do not allow the application
9928 * to change the selection foreground color.
9929 * </p>
9930 * @param color the new color (or null)
9931 *
9932 * @exception IllegalArgumentException <ul>
9933 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9934 * </ul>
9935 * @exception SWTException <ul>
9936 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9937 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9938 * </ul>
9939 * @since 2.1
9940 */
setSelectionForeground(Color color)9941 public void setSelectionForeground (Color color) {
9942 checkWidget ();
9943 if (color != null) {
9944 if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9945 }
9946 selectionForeground = color;
9947 resetCache(0, content.getLineCount());
9948 setCaretLocation();
9949 super.redraw();
9950 }
9951 /**
9952 * Sets the selection and scrolls it into view.
9953 * <p>
9954 * Indexing is zero based. Text selections are specified in terms of
9955 * caret positions. In a text widget that contains N characters, there are
9956 * N+1 caret positions, ranging from 0..N
9957 * </p>
9958 *
9959 * @param start selection start offset. The caret will be placed at the
9960 * selection start when start > end.
9961 * @param end selection end offset
9962 * @see #setSelectionRange(int,int)
9963 * @exception SWTException <ul>
9964 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9965 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9966 * </ul>
9967 * @exception IllegalArgumentException <ul>
9968 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9969 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9970 * </ul>
9971 */
setSelection(int start, int end)9972 public void setSelection(int start, int end) {
9973 setSelectionRange(start, end - start);
9974 showSelection();
9975 }
9976 /**
9977 * Sets the selection.
9978 * <p>
9979 * The new selection may not be visible. Call showSelection to scroll
9980 * the selection into view.
9981 * </p>
9982 *
9983 * @param start offset of the first selected character, start >= 0 must be true.
9984 * @param length number of characters to select, 0 <= start + length
9985 * <= getCharCount() must be true.
9986 * A negative length places the caret at the selection start.
9987 * @param sendEvent a Selection event is sent when set to true and when
9988 * the selection is reset.
9989 */
setSelection(int start, int length, boolean sendEvent, boolean doBlock)9990 void setSelection(int start, int length, boolean sendEvent, boolean doBlock) {
9991 int end = start + length;
9992 if (start > end) {
9993 int temp = end;
9994 end = start;
9995 start = temp;
9996 }
9997 // is the selection range different or is the selection direction
9998 // different?
9999 if (selection.x != start || selection.y != end ||
10000 (length > 0 && selectionAnchor != selection.x) ||
10001 (length < 0 && selectionAnchor != selection.y)) {
10002 if (blockSelection && doBlock) {
10003 if (length < 0) {
10004 setBlockSelectionOffset(end, start, sendEvent);
10005 } else {
10006 setBlockSelectionOffset(start, end, sendEvent);
10007 }
10008 } else {
10009 int oldStart = selection.x;
10010 int oldLength = selection.y - selection.x;
10011 int charCount = content.getCharCount();
10012 // called internally to remove selection after text is removed
10013 // therefore make sure redraw range is valid.
10014 int redrawX = Math.min(selection.x, charCount);
10015 int redrawY = Math.min(selection.y, charCount);
10016 if (length < 0) {
10017 selectionAnchor = selection.y = end;
10018 selection.x = start;
10019 setCaretOffset(start, PREVIOUS_OFFSET_TRAILING);
10020 } else {
10021 selectionAnchor = selection.x = start;
10022 selection.y = end;
10023 setCaretOffset(end, PREVIOUS_OFFSET_TRAILING);
10024 }
10025 redrawX = Math.min(redrawX, selection.x);
10026 redrawY = Math.max(redrawY, selection.y);
10027 if (redrawY - redrawX > 0) {
10028 internalRedrawRange(redrawX, redrawY - redrawX);
10029 }
10030 if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) {
10031 sendSelectionEvent();
10032 }
10033 sendAccessibleTextCaretMoved();
10034 }
10035 }
10036 }
10037 /**
10038 * Sets the selection.
10039 * <p>
10040 * The new selection may not be visible. Call showSelection to scroll the selection
10041 * into view. A negative length places the caret at the visual start of the selection.
10042 * </p>
10043 *
10044 * @param start offset of the first selected character
10045 * @param length number of characters to select
10046 *
10047 * @exception SWTException <ul>
10048 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10049 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10050 * </ul>
10051 * @exception IllegalArgumentException <ul>
10052 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
10053 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
10054 * </ul>
10055 */
setSelectionRange(int start, int length)10056 public void setSelectionRange(int start, int length) {
10057 checkWidget();
10058 int contentLength = getCharCount();
10059 start = Math.max(0, Math.min (start, contentLength));
10060 int end = start + length;
10061 if (end < 0) {
10062 length = -start;
10063 } else {
10064 if (end > contentLength) length = contentLength - start;
10065 }
10066 if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
10067 // the start offset or end offset of the selection range is inside a
10068 // multi byte line delimiter. This is an illegal operation and an exception
10069 // is thrown. Fixes 1GDKK3R
10070 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10071 }
10072 setSelection(start, length, false, true);
10073 setCaretLocation();
10074 }
10075 /**
10076 * Adds the specified style.
10077 * <p>
10078 * The new style overwrites existing styles for the specified range.
10079 * Existing style ranges are adjusted if they partially overlap with
10080 * the new style. To clear an individual style, call setStyleRange
10081 * with a StyleRange that has null attributes.
10082 * </p><p>
10083 * Should not be called if a LineStyleListener has been set since the
10084 * listener maintains the styles.
10085 * </p>
10086 *
10087 * @param range StyleRange object containing the style information.
10088 * Overwrites the old style in the given range. May be null to delete
10089 * all styles.
10090 * @exception SWTException <ul>
10091 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10092 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10093 * </ul>
10094 * @exception IllegalArgumentException <ul>
10095 * <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li>
10096 * </ul>
10097 */
setStyleRange(StyleRange range)10098 public void setStyleRange(StyleRange range) {
10099 checkWidget();
10100 if (isListening(ST.LineGetStyle)) return;
10101 if (range != null) {
10102 if (range.isUnstyled()) {
10103 setStyleRanges(range.start, range.length, null, null, false);
10104 } else {
10105 setStyleRanges(range.start, 0, null, new StyleRange[]{range}, false);
10106 }
10107 } else {
10108 setStyleRanges(0, 0, null, null, true);
10109 }
10110 }
10111 /**
10112 * Clears the styles in the range specified by <code>start</code> and
10113 * <code>length</code> and adds the new styles.
10114 * <p>
10115 * The ranges array contains start and length pairs. Each pair refers to
10116 * the corresponding style in the styles array. For example, the pair
10117 * that starts at ranges[n] with length ranges[n+1] uses the style
10118 * at styles[n/2]. The range fields within each StyleRange are ignored.
10119 * If ranges or styles is null, the specified range is cleared.
10120 * </p><p>
10121 * Note: It is expected that the same instance of a StyleRange will occur
10122 * multiple times within the styles array, reducing memory usage.
10123 * </p><p>
10124 * Should not be called if a LineStyleListener has been set since the
10125 * listener maintains the styles.
10126 * </p>
10127 *
10128 * @param start offset of first character where styles will be deleted
10129 * @param length length of the range to delete styles in
10130 * @param ranges the array of ranges. The ranges must not overlap and must be in order.
10131 * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
10132 *
10133 * @exception SWTException <ul>
10134 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10135 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10136 * </ul>
10137 * @exception IllegalArgumentException <ul>
10138 * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
10139 * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
10140 * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
10141 * <li>ERROR_INVALID_RANGE when a range overlaps</li>
10142 * </ul>
10143 *
10144 * @since 3.2
10145 */
setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles)10146 public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
10147 checkWidget();
10148 if (isListening(ST.LineGetStyle)) return;
10149 if (ranges == null || styles == null) {
10150 setStyleRanges(start, length, null, null, false);
10151 } else {
10152 setStyleRanges(start, length, ranges, styles, false);
10153 }
10154 }
10155 /**
10156 * Sets styles to be used for rendering the widget content.
10157 * <p>
10158 * All styles in the widget will be replaced with the given set of ranges and styles.
10159 * The ranges array contains start and length pairs. Each pair refers to
10160 * the corresponding style in the styles array. For example, the pair
10161 * that starts at ranges[n] with length ranges[n+1] uses the style
10162 * at styles[n/2]. The range fields within each StyleRange are ignored.
10163 * If either argument is null, the styles are cleared.
10164 * </p><p>
10165 * Note: It is expected that the same instance of a StyleRange will occur
10166 * multiple times within the styles array, reducing memory usage.
10167 * </p><p>
10168 * Should not be called if a LineStyleListener has been set since the
10169 * listener maintains the styles.
10170 * </p>
10171 *
10172 * @param ranges the array of ranges. The ranges must not overlap and must be in order.
10173 * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
10174 *
10175 * @exception SWTException <ul>
10176 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10177 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10178 * </ul>
10179 * @exception IllegalArgumentException <ul>
10180 * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
10181 * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
10182 * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
10183 * <li>ERROR_INVALID_RANGE when a range overlaps</li>
10184 * </ul>
10185 *
10186 * @since 3.2
10187 */
setStyleRanges(int[] ranges, StyleRange[] styles)10188 public void setStyleRanges(int[] ranges, StyleRange[] styles) {
10189 checkWidget();
10190 if (isListening(ST.LineGetStyle)) return;
10191 if (ranges == null || styles == null) {
10192 setStyleRanges(0, 0, null, null, true);
10193 } else {
10194 setStyleRanges(0, 0, ranges, styles, true);
10195 }
10196 }
setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset)10197 void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) {
10198 int charCount = content.getCharCount();
10199 if (reset) {
10200 start = 0;
10201 length = charCount;
10202 }
10203 int[] formerRanges = getRanges(start, length);
10204 StyleRange[] formerStyles = getStyleRanges(start, length);
10205 int end = start + length;
10206 final boolean wasFixedLineHeight = isFixedLineHeight();
10207 if (start > end || start < 0) {
10208 SWT.error(SWT.ERROR_INVALID_RANGE);
10209 }
10210 if (styles != null) {
10211 if (end > charCount) {
10212 SWT.error(SWT.ERROR_INVALID_RANGE);
10213 }
10214 if (ranges != null) {
10215 if (ranges.length != styles.length << 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10216 }
10217 int lastOffset = 0;
10218 for (int i = 0; i < styles.length; i ++) {
10219 if (styles[i] == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10220 int rangeStart, rangeLength;
10221 if (ranges != null) {
10222 rangeStart = ranges[i << 1];
10223 rangeLength = ranges[(i << 1) + 1];
10224 } else {
10225 rangeStart = styles[i].start;
10226 rangeLength = styles[i].length;
10227 }
10228 if (rangeLength < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10229 if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10230 if (lastOffset > rangeStart) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10231 hasStyleWithVariableHeight |= styles[i].isVariableHeight();
10232 lastOffset = rangeStart + rangeLength;
10233 }
10234 }
10235 int rangeStart = start, rangeEnd = end;
10236 if (styles != null && styles.length > 0) {
10237 if (ranges != null) {
10238 rangeStart = ranges[0];
10239 rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
10240 } else {
10241 rangeStart = styles[0].start;
10242 rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
10243 }
10244 }
10245
10246 // This needs to happen before new styles are applied
10247 int expectedBottom = 0;
10248 if (!isFixedLineHeight() && !reset) {
10249 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
10250 int partialTopIndex = getPartialTopIndex();
10251 int partialBottomIndex = getPartialBottomIndex();
10252 if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
10253 expectedBottom = getLinePixel(lineEnd + 1);
10254 }
10255 }
10256 if (reset) {
10257 renderer.setStyleRanges(null, null);
10258 } else {
10259 renderer.updateRanges(start, length, length);
10260 }
10261 if (styles != null && styles.length > 0) {
10262 renderer.setStyleRanges(ranges, styles);
10263 }
10264
10265 // re-evaluate variable height with all styles (including new ones)
10266 hasStyleWithVariableHeight = false;
10267 for (StyleRange style : getStyleRanges(false)) {
10268 hasStyleWithVariableHeight = style.isVariableHeight();
10269 if (hasStyleWithVariableHeight) break;
10270 }
10271
10272 SortedSet<Integer> modifiedLines = computeModifiedLines(formerRanges, formerStyles, ranges, styles);
10273 resetCache(modifiedLines);
10274 if (reset) {
10275 super.redraw();
10276 } else {
10277 int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
10278 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
10279 int partialTopIndex = getPartialTopIndex();
10280 int partialBottomIndex = getPartialBottomIndex();
10281 if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
10282 int top = 0;
10283 int bottom = clientAreaHeight;
10284 if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
10285 top = Math.max(0, getLinePixel(lineStart));
10286 }
10287 if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
10288 bottom = getLinePixel(lineEnd + 1);
10289 }
10290 if (!(wasFixedLineHeight && isFixedLineHeight()) && bottom != expectedBottom) {
10291 bottom = clientAreaHeight;
10292 }
10293 super.redraw(0, top, clientAreaWidth, bottom - top, false);
10294 }
10295 }
10296 int oldColumnX = columnX;
10297 setCaretLocation();
10298 columnX = oldColumnX;
10299 doMouseLinkCursor();
10300 }
10301
10302 /**
10303 *
10304 * @param referenceRanges former ranges, sorted by order and without overlapping, typically returned {@link #getRanges(int, int)}
10305 * @param referenceStyles
10306 * @param newRanges former ranges, sorted by order and without overlapping
10307 * @param newStyles
10308 * @return
10309 */
computeModifiedLines(int[] referenceRanges, StyleRange[] referenceStyles, int[] newRanges, StyleRange[] newStyles)10310 private SortedSet<Integer> computeModifiedLines(int[] referenceRanges, StyleRange[] referenceStyles, int[] newRanges, StyleRange[] newStyles) {
10311 if (referenceStyles == null) {
10312 referenceStyles = new StyleRange[0];
10313 }
10314 if (referenceRanges == null) {
10315 referenceRanges = createRanges(referenceStyles);
10316 }
10317 if (newStyles == null) {
10318 newStyles = new StyleRange[0];
10319 }
10320 if (newRanges == null) {
10321 newRanges = createRanges(newStyles);
10322 }
10323 if (referenceRanges.length != 2 * referenceStyles.length) {
10324 throw new IllegalArgumentException();
10325 }
10326 if (newRanges.length != 2 * newStyles.length) {
10327 throw new IllegalArgumentException();
10328 }
10329 SortedSet<Integer> res = new TreeSet<>();
10330 int referenceRangeIndex = 0;
10331 int newRangeIndex = 0;
10332 StyleRange defaultStyle = new StyleRange();
10333 defaultStyle.foreground = this.foreground;
10334 defaultStyle.background = this.background;
10335 defaultStyle.font = getFont();
10336 int currentOffset = referenceRanges.length > 0 ? referenceRanges[0] : Integer.MAX_VALUE;
10337 if (newRanges.length > 0) {
10338 currentOffset = Math.min(currentOffset, newRanges[0]);
10339 }
10340 while (currentOffset < content.getCharCount() && (referenceRangeIndex < referenceStyles.length || newRangeIndex < newRanges.length)) {
10341 int nextMilestoneOffset = Integer.MAX_VALUE; // next new range start/end after current offset
10342
10343 while (referenceRangeIndex < referenceStyles.length && endRangeOffset(referenceRanges, referenceRangeIndex) <= currentOffset) {
10344 referenceRangeIndex++;
10345 }
10346 StyleRange referenceStyleAtCurrentOffset = defaultStyle;
10347 if (isInRange(referenceRanges, referenceRangeIndex, currentOffset)) { // has styling
10348 referenceStyleAtCurrentOffset = referenceStyles[referenceRangeIndex];
10349 nextMilestoneOffset = endRangeOffset(referenceRanges, referenceRangeIndex);
10350 } else if (referenceRangeIndex < referenceStyles.length) { // no range, default styling
10351 nextMilestoneOffset = referenceRanges[2 * referenceRangeIndex]; // beginning of next range
10352 }
10353
10354 while (newRangeIndex < newStyles.length && endRangeOffset(newRanges, newRangeIndex) <= currentOffset) {
10355 newRangeIndex++;
10356 }
10357 StyleRange newStyleAtCurrentOffset = defaultStyle;
10358 if (isInRange(newRanges, newRangeIndex, currentOffset)) {
10359 newStyleAtCurrentOffset = newStyles[newRangeIndex];
10360 nextMilestoneOffset = Math.min(nextMilestoneOffset, endRangeOffset(newRanges, newRangeIndex));
10361 } else if (newRangeIndex < newStyles.length) {
10362 nextMilestoneOffset = Math.min(nextMilestoneOffset, newRanges[2 * newRangeIndex]);
10363 }
10364
10365 if (!referenceStyleAtCurrentOffset.similarTo(newStyleAtCurrentOffset)) {
10366 int fromLine = getLineAtOffset(currentOffset);
10367 int toLine = getLineAtOffset(nextMilestoneOffset - 1);
10368 for (int line = fromLine; line <= toLine; line++) {
10369 res.add(line);
10370 }
10371 currentOffset = toLine + 1 < getLineCount() ? getOffsetAtLine(toLine + 1) : content.getCharCount();
10372 } else {
10373 currentOffset = nextMilestoneOffset;
10374 }
10375 }
10376 return res;
10377 }
10378 private int[] createRanges(StyleRange[] referenceStyles) {
10379 int[] referenceRanges;
10380 referenceRanges = new int[2 * referenceStyles.length];
10381 for (int i = 0; i < referenceStyles.length; i++) {
10382 referenceRanges[2 * i] = referenceStyles[i].start;
10383 referenceRanges[2 * i + 1] = referenceStyles[i].length;
10384 }
10385 return referenceRanges;
10386 }
10387
10388 private boolean isInRange(int[] ranges, int styleIndex, int offset) {
10389 if (ranges == null || ranges.length == 0 || styleIndex < 0 || 2 * styleIndex + 1 > ranges.length) {
10390 return false;
10391 }
10392 int start = ranges[2 * styleIndex];
10393 int length = ranges[2 * styleIndex + 1];
10394 return offset >= start && offset < start + length;
10395 }
10396
10397 /**
10398 * The offset on which the range ends (excluded)
10399 * @param ranges
10400 * @param styleIndex
10401 * @return
10402 */
10403 private int endRangeOffset(int[] ranges, int styleIndex) {
10404 if (styleIndex < 0 || 2 * styleIndex > ranges.length) {
10405 throw new IllegalArgumentException();
10406 }
10407 int start = ranges[2 * styleIndex];
10408 int length = ranges[2 * styleIndex + 1];
10409 return start + length;
10410 }
10411
10412 /**
10413 * Sets styles to be used for rendering the widget content. All styles
10414 * in the widget will be replaced with the given set of styles.
10415 * <p>
10416 * Note: Because a StyleRange includes the start and length, the
10417 * same instance cannot occur multiple times in the array of styles.
10418 * If the same style attributes, such as font and color, occur in
10419 * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
10420 * can be used to share styles and reduce memory usage.
10421 * </p><p>
10422 * Should not be called if a LineStyleListener has been set since the
10423 * listener maintains the styles.
10424 * </p>
10425 *
10426 * @param ranges StyleRange objects containing the style information.
10427 * The ranges should not overlap. The style rendering is undefined if
10428 * the ranges do overlap. Must not be null. The styles need to be in order.
10429 * @exception SWTException <ul>
10430 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10431 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10432 * </ul>
10433 * @exception IllegalArgumentException <ul>
10434 * <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li>
10435 * <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
10436 * </ul>
10437 *
10438 * @see #setStyleRanges(int[], StyleRange[])
10439 */
10440 public void setStyleRanges(StyleRange[] ranges) {
10441 checkWidget();
10442 if (isListening(ST.LineGetStyle)) return;
10443 if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
10444 setStyleRanges(0, 0, null, ranges, true);
10445 }
10446 /**
10447 * Sets the tab width.
10448 *
10449 * @param tabs tab width measured in characters.
10450 * @exception SWTException <ul>
10451 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10452 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10453 * </ul>
10454 *
10455 * @see #setTabStops(int[])
10456 */
10457 public void setTabs(int tabs) {
10458 checkWidget();
10459 tabLength = tabs;
10460 renderer.setFont(null, tabs);
10461 resetCache(0, content.getLineCount());
10462 setCaretLocation();
10463 super.redraw();
10464 }
10465
10466 /**
10467 * Sets the receiver's tab list. Each value in the tab list specifies
10468 * the space in points from the origin of the document to the respective
10469 * tab stop. The last tab stop width is repeated continuously.
10470 *
10471 * @param tabs the new tab list (or null)
10472 *
10473 * @exception SWTException <ul>
10474 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10475 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10476 * </ul>
10477 * @exception IllegalArgumentException <ul>
10478 * <li>ERROR_INVALID_ARGUMENT - if a tab stop is negative or less than the previous stop in the list</li>
10479 * </ul>
10480 *
10481 * @see StyledText#getTabStops()
10482 *
10483 * @since 3.6
10484 */
10485 public void setTabStops(int [] tabs) {
10486 checkWidget();
10487 if (tabs != null) {
10488 int pos = 0;
10489 int[] newTabs = new int[tabs.length];
10490 for (int i = 0; i < tabs.length; i++) {
10491 if (tabs[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10492 newTabs[i] = pos = tabs[i];
10493 }
10494 this.tabs = newTabs;
10495 } else {
10496 this.tabs = null;
10497 }
10498 resetCache(0, content.getLineCount());
10499 setCaretLocation();
10500 super.redraw();
10501 }
10502
10503 /**
10504 * Sets the widget content.
10505 * If the widget has the SWT.SINGLE style and "text" contains more than
10506 * one line, only the first line is rendered but the text is stored
10507 * unchanged. A subsequent call to getText will return the same text
10508 * that was set.
10509 * <p>
10510 * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE
10511 * style is used.
10512 * </p>
10513 *
10514 * @param text new widget content. Replaces existing content. Line styles
10515 * that were set using StyledText API are discarded. The
10516 * current selection is also discarded.
10517 * @exception SWTException <ul>
10518 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10519 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10520 * </ul>
10521 * @exception IllegalArgumentException <ul>
10522 * <li>ERROR_NULL_ARGUMENT when string is null</li>
10523 * </ul>
10524 */
10525 public void setText(String text) {
10526 checkWidget();
10527 if (text == null) {
10528 SWT.error(SWT.ERROR_NULL_ARGUMENT);
10529 }
10530 Event event = new Event();
10531 event.start = 0;
10532 event.end = getCharCount();
10533 event.text = text;
10534 event.doit = true;
10535 notifyListeners(SWT.Verify, event);
10536 if (event.doit) {
10537 StyledTextEvent styledTextEvent = null;
10538 if (isListening(ST.ExtendedModify)) {
10539 styledTextEvent = new StyledTextEvent(content);
10540 styledTextEvent.start = event.start;
10541 styledTextEvent.end = event.start + event.text.length();
10542 styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
10543 }
10544 content.setText(event.text);
10545 notifyListeners(SWT.Modify, event);
10546 if (styledTextEvent != null) {
10547 notifyListeners(ST.ExtendedModify, styledTextEvent);
10548 }
10549 }
10550 }
10551
10552 /**
10553 * Sets the base text direction (a.k.a. "paragraph direction") of the receiver,
10554 * which must be one of the constants <code>SWT.LEFT_TO_RIGHT</code> or
10555 * <code>SWT.RIGHT_TO_LEFT</code>.
10556 * <p>
10557 * <code>setOrientation</code> would override this value with the text direction
10558 * that is consistent with the new orientation.
10559 * </p>
10560 * <p>
10561 * <b>Warning</b>: This API is currently only implemented on Windows.
10562 * It doesn't set the base text direction on GTK and Cocoa.
10563 * </p>
10564 *
10565 * @param textDirection the base text direction style
10566 *
10567 * @exception SWTException <ul>
10568 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10569 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10570 * </ul>
10571 *
10572 * @see SWT#FLIP_TEXT_DIRECTION
10573 */
10574 @Override
10575 public void setTextDirection(int textDirection) {
10576 checkWidget();
10577 int oldStyle = getStyle();
10578 super.setTextDirection(textDirection);
10579 if (isAutoDirection () || oldStyle != getStyle()) {
10580 resetBidiData();
10581 }
10582 }
10583
10584 /**
10585 * Sets the text limit to the specified number of characters.
10586 * <p>
10587 * The text limit specifies the amount of text that
10588 * the user can type into the widget.
10589 * </p>
10590 *
10591 * @param limit the new text limit.
10592 * @exception SWTException <ul>
10593 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10594 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10595 * </ul>
10596 * @exception IllegalArgumentException <ul>
10597 * <li>ERROR_CANNOT_BE_ZERO when limit is 0</li>
10598 * </ul>
10599 */
10600 public void setTextLimit(int limit) {
10601 checkWidget();
10602 if (limit == 0) {
10603 SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
10604 }
10605 textLimit = limit;
10606 }
10607 /**
10608 * Sets the top index. Do nothing if there is no text set.
10609 * <p>
10610 * The top index is the index of the line that is currently at the top
10611 * of the widget. The top index changes when the widget is scrolled.
10612 * Indexing starts from zero.
10613 * Note: The top index is reset to 0 when new text is set in the widget.
10614 * </p>
10615 *
10616 * @param topIndex new top index. Must be between 0 and
10617 * getLineCount() - fully visible lines per page. If no lines are fully
10618 * visible the maximum value is getLineCount() - 1. An out of range
10619 * index will be adjusted accordingly.
10620 * @exception SWTException <ul>
10621 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10622 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10623 * </ul>
10624 */
10625 public void setTopIndex(int topIndex) {
10626 checkWidget();
10627 if (getCharCount() == 0) {
10628 return;
10629 }
10630 int lineCount = content.getLineCount(), pixel;
10631 if (isFixedLineHeight()) {
10632 int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
10633 if (topIndex < 0) {
10634 topIndex = 0;
10635 } else if (topIndex > lineCount - pageSize) {
10636 topIndex = lineCount - pageSize;
10637 }
10638 pixel = getLinePixel(topIndex);
10639 } else {
10640 topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
10641 pixel = getLinePixel(topIndex);
10642 if (pixel > 0) {
10643 pixel = getAvailableHeightBellow(pixel);
10644 } else {
10645 pixel = getAvailableHeightAbove(pixel);
10646 }
10647 }
10648 scrollVertical(pixel, true);
10649 }
10650 /**
10651 * Sets the top margin.
10652 *
10653 * @param topMargin the top margin.
10654 * @exception SWTException <ul>
10655 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10656 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10657 * </ul>
10658 *
10659 * @since 3.5
10660 */
10661 public void setTopMargin (int topMargin) {
10662 checkWidget();
10663 setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
10664 }
10665 /**
10666 * Sets the top SWT logical point offset. Do nothing if there is no text set.
10667 * <p>
10668 * The top point offset is the vertical SWT logical point offset of the widget. The
10669 * widget is scrolled so that the given SWT logical point position is at the top.
10670 * The top index is adjusted to the corresponding top line.
10671 * Note: The top point is reset to 0 when new text is set in the widget.
10672 * </p>
10673 *
10674 * @param pixel new top point offset. Must be between 0 and
10675 * (getLineCount() - visible lines per page) / getLineHeight()). An out
10676 * of range offset will be adjusted accordingly.
10677 * @exception SWTException <ul>
10678 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10679 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10680 * </ul>
10681 * @since 2.0
10682 */
10683 public void setTopPixel(int pixel) {
10684 checkWidget();
10685 if (getCharCount() == 0) {
10686 return;
10687 }
10688 if (pixel < 0) pixel = 0;
10689 int lineCount = content.getLineCount();
10690 int height = clientAreaHeight - topMargin - bottomMargin;
10691 int verticalOffset = getVerticalScrollOffset();
10692 if (isFixedLineHeight()) {
10693 int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
10694 if (pixel > maxTopPixel) pixel = maxTopPixel;
10695 pixel -= verticalOffset;
10696 } else {
10697 pixel -= verticalOffset;
10698 if (pixel > 0) {
10699 pixel = getAvailableHeightBellow(pixel);
10700 }
10701 }
10702 scrollVertical(pixel, true);
10703 }
10704 /**
10705 * Sets whether the widget wraps lines.
10706 * <p>
10707 * This overrides the creation style bit SWT.WRAP.
10708 * </p>
10709 *
10710 * @param wrap true=widget wraps lines, false=widget does not wrap lines
10711 * @since 2.0
10712 */
10713 public void setWordWrap(boolean wrap) {
10714 checkWidget();
10715 if ((getStyle() & SWT.SINGLE) != 0) return;
10716 if (wordWrap == wrap) return;
10717 if (wordWrap && blockSelection) setBlockSelection(false);
10718 wordWrap = wrap;
10719 resetCache(0, content.getLineCount());
10720 horizontalScrollOffset = 0;
10721 ScrollBar horizontalBar = getHorizontalBar();
10722 if (horizontalBar != null) {
10723 horizontalBar.setVisible(!wordWrap);
10724 }
10725 setScrollBars(true);
10726 setCaretLocation();
10727 super.redraw();
10728 }
10729 /**
10730 * Sets the wrap line indentation of the widget.
10731 * <p>
10732 * It is the amount of blank space, in points, at the beginning of each wrapped line.
10733 * When a line wraps in several lines all the lines but the first one is indented
10734 * by this amount.
10735 * </p>
10736 *
10737 * @param wrapIndent the new wrap indent
10738 *
10739 * @exception SWTException <ul>
10740 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10741 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10742 * </ul>
10743 *
10744 * @see #setLineWrapIndent(int, int, int)
10745 *
10746 * @since 3.6
10747 */
10748 public void setWrapIndent(int wrapIndent) {
10749 checkWidget();
10750 if (this.wrapIndent == wrapIndent || wrapIndent < 0) return;
10751 this.wrapIndent = wrapIndent;
10752 resetCache(0, content.getLineCount());
10753 setCaretLocation();
10754 super.redraw();
10755 }
10756 boolean showLocation(Rectangle rect, boolean scrollPage) {
10757 boolean scrolled = false;
10758 if (rect.y < topMargin) {
10759 scrolled = scrollVertical(rect.y - topMargin, true);
10760 } else if (rect.y + rect.height > clientAreaHeight - bottomMargin) {
10761 if (clientAreaHeight - topMargin - bottomMargin <= 0) {
10762 scrolled = scrollVertical(rect.y - topMargin, true);
10763 } else {
10764 scrolled = scrollVertical(rect.y + rect.height - (clientAreaHeight - bottomMargin), true);
10765 }
10766 }
10767 int width = clientAreaWidth - rightMargin - leftMargin;
10768 if (width > 0) {
10769 int minScroll = scrollPage ? width / 4 : 0;
10770 if (rect.x < leftMargin) {
10771 int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
10772 int maxScroll = horizontalScrollOffset;
10773 scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
10774 } else if (rect.x + rect.width > (clientAreaWidth - rightMargin)) {
10775 int scrollWidth = Math.max(rect.x + rect.width - (clientAreaWidth - rightMargin), minScroll);
10776 int maxScroll = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
10777 scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
10778 }
10779 }
10780 return scrolled;
10781 }
10782 /**
10783 * Sets the caret location and scrolls the caret offset into view.
10784 */
10785 void showCaret() {
10786 Rectangle bounds = getBoundsAtOffset(caretOffset);
10787 if (!showLocation(bounds, true)) {
10788 setCaretLocation();
10789 }
10790 }
10791 /**
10792 * Scrolls the selection into view.
10793 * <p>
10794 * The end of the selection will be scrolled into view.
10795 * Note that if a right-to-left selection exists, the end of the selection is
10796 * the visual beginning of the selection (i.e., where the caret is located).
10797 * </p>
10798 *
10799 * @exception SWTException <ul>
10800 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10801 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10802 * </ul>
10803 */
10804 public void showSelection() {
10805 checkWidget();
10806 // is selection from right-to-left?
10807 boolean rightToLeft = caretOffset == selection.x;
10808 int startOffset, endOffset;
10809 if (rightToLeft) {
10810 startOffset = selection.y;
10811 endOffset = selection.x;
10812 } else {
10813 startOffset = selection.x;
10814 endOffset = selection.y;
10815 }
10816
10817 Rectangle startBounds = getBoundsAtOffset(startOffset);
10818 Rectangle endBounds = getBoundsAtOffset(endOffset);
10819
10820 // can the selection be fully displayed within the widget's visible width?
10821 int w = clientAreaWidth - leftMargin - rightMargin;
10822 boolean selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
10823 if (selectionFits) {
10824 // show as much of the selection as possible by first showing
10825 // the start of the selection
10826 if (showLocation(startBounds, false)) {
10827 // endX value could change if showing startX caused a scroll to occur
10828 endBounds = getBoundsAtOffset(endOffset);
10829 }
10830 // the character at endOffset is not part of the selection
10831 endBounds.width = endOffset == caretOffset ? getCaretWidth() : 0;
10832 showLocation(endBounds, false);
10833 } else {
10834 // just show the end of the selection since the selection start
10835 // will not be visible
10836 showLocation(endBounds, true);
10837 }
10838 }
10839 void updateCaretVisibility() {
10840 Caret caret = getCaret();
10841 if (caret != null) {
10842 if (blockSelection && blockXLocation != -1) {
10843 caret.setVisible(false);
10844 } else {
10845 Point location = caret.getLocation();
10846 Point size = caret.getSize();
10847 boolean visible =
10848 topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin &&
10849 leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin;
10850 caret.setVisible(visible);
10851 }
10852 }
10853 }
10854 /**
10855 * Updates the selection and caret position depending on the text change.
10856 * <p>
10857 * If the selection intersects with the replaced text, the selection is
10858 * reset and the caret moved to the end of the new text.
10859 * If the selection is behind the replaced text it is moved so that the
10860 * same text remains selected. If the selection is before the replaced text
10861 * it is left unchanged.
10862 * </p>
10863 *
10864 * @param startOffset offset of the text change
10865 * @param replacedLength length of text being replaced
10866 * @param newLength length of new text
10867 */
10868 void updateSelection(int startOffset, int replacedLength, int newLength) {
10869 if (selection.y <= startOffset) {
10870 // selection ends before text change
10871 if (isWordWrap()) setCaretLocation();
10872 return;
10873 }
10874 if (selection.x < startOffset) {
10875 // clear selection fragment before text change
10876 internalRedrawRange(selection.x, startOffset - selection.x);
10877 }
10878 if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
10879 // clear selection fragment after text change.
10880 // do this only when the selection is actually affected by the
10881 // change. Selection is only affected if it intersects the change (1GDY217).
10882 int netNewLength = newLength - replacedLength;
10883 int redrawStart = startOffset + newLength;
10884 internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
10885 }
10886 if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
10887 // selection intersects replaced text. set caret behind text change
10888 setSelection(startOffset + newLength, 0, true, false);
10889 } else {
10890 // move selection to keep same text selected
10891 setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true, false);
10892 }
10893 setCaretLocation();
10894 }
10895 }
10896